Require dual signatures for aftersales confirmation
This commit is contained in:
+121
-45
@@ -29,8 +29,11 @@ function AftersalesConfirmPage() {
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [rejectReason, setRejectReason] = useState('');
|
||||
const [showRejectDialog, setShowRejectDialog] = useState(false);
|
||||
const [signatureData, setSignatureData] = useState('');
|
||||
const [showSignatureOverlay, setShowSignatureOverlay] = useState(false);
|
||||
const [customerSignatureData, setCustomerSignatureData] = useState('');
|
||||
const [responsibleSignatureData, setResponsibleSignatureData] = useState('');
|
||||
const [activeSignatureRole, setActiveSignatureRole] = useState<'customer' | 'responsible' | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const loadOrder = async () => {
|
||||
setLoading(true);
|
||||
@@ -53,15 +56,20 @@ function AftersalesConfirmPage() {
|
||||
}, [serialNumber]);
|
||||
|
||||
const handleAuthorize = async () => {
|
||||
if (!signatureData) {
|
||||
message.error('请先签名');
|
||||
if (!customerSignatureData) {
|
||||
message.error('请先完成客户签名');
|
||||
return;
|
||||
}
|
||||
if (!responsibleSignatureData) {
|
||||
message.error('请先完成负责人签名');
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const updated = await aftersalesApi.customerConfirm(serialNumber, {
|
||||
action: 'authorize',
|
||||
signature: signatureData,
|
||||
signature: customerSignatureData,
|
||||
responsibleSignature: responsibleSignatureData,
|
||||
});
|
||||
setOrder(updated);
|
||||
message.success('感谢您的确认,工单已完成');
|
||||
@@ -99,8 +107,16 @@ function AftersalesConfirmPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearSignature = () => {
|
||||
setSignatureData('');
|
||||
const handleClearSignature = (role: 'customer' | 'responsible') => {
|
||||
if (role === 'customer') {
|
||||
setCustomerSignatureData('');
|
||||
return;
|
||||
}
|
||||
setResponsibleSignatureData('');
|
||||
};
|
||||
|
||||
const openSignatureOverlay = (role: 'customer' | 'responsible') => {
|
||||
setActiveSignatureRole(role);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
@@ -220,48 +236,103 @@ function AftersalesConfirmPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isClosed && order.signature && (
|
||||
{isClosed && (order.signature || order.responsibleSignature) && (
|
||||
<div className="aftersales-signature-archived">
|
||||
<p className="aftersales-signature-tip">客户确认签名</p>
|
||||
<img
|
||||
src={order.signature}
|
||||
alt="客户确认签名"
|
||||
className="aftersales-signature-archived-img"
|
||||
/>
|
||||
<div className="aftersales-signature-grid">
|
||||
{order.signature && (
|
||||
<div className="aftersales-signature-archived-item">
|
||||
<p className="aftersales-signature-tip">客户签名</p>
|
||||
<img
|
||||
src={order.signature}
|
||||
alt="客户签名"
|
||||
className="aftersales-signature-archived-img"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{order.responsibleSignature && (
|
||||
<div className="aftersales-signature-archived-item">
|
||||
<p className="aftersales-signature-tip">负责人签名</p>
|
||||
<img
|
||||
src={order.responsibleSignature}
|
||||
alt="负责人签名"
|
||||
className="aftersales-signature-archived-img"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isPending && (
|
||||
<>
|
||||
<div className="aftersales-signature-section">
|
||||
<div className="aftersales-signature-header">
|
||||
<p className="aftersales-signature-tip">请签名确认维修结果</p>
|
||||
{signatureData && (
|
||||
<Button size="small" type="link" onClick={handleClearSignature}>
|
||||
清除签名
|
||||
</Button>
|
||||
)}
|
||||
<p className="aftersales-signature-section-title">请签名确认维修结果</p>
|
||||
<div className="aftersales-signature-grid">
|
||||
<div className="aftersales-signature-item">
|
||||
<div className="aftersales-signature-header">
|
||||
<p className="aftersales-signature-tip">客户签名</p>
|
||||
{customerSignatureData && (
|
||||
<Button size="small" type="link" onClick={() => handleClearSignature('customer')}>
|
||||
清除
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{customerSignatureData ? (
|
||||
<button
|
||||
type="button"
|
||||
className="aftersales-signature-preview"
|
||||
onClick={() => openSignatureOverlay('customer')}
|
||||
>
|
||||
<img src={customerSignatureData} alt="客户签名" />
|
||||
<span className="aftersales-signature-preview-hint">点击可重新签名</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="aftersales-signature-trigger"
|
||||
onClick={() => openSignatureOverlay('customer')}
|
||||
>
|
||||
<EditOutlined />
|
||||
<span>客户签名</span>
|
||||
<small>将进入签名页</small>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="aftersales-signature-item">
|
||||
<div className="aftersales-signature-header">
|
||||
<p className="aftersales-signature-tip">负责人签名</p>
|
||||
{responsibleSignatureData && (
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
onClick={() => handleClearSignature('responsible')}
|
||||
>
|
||||
清除
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{responsibleSignatureData ? (
|
||||
<button
|
||||
type="button"
|
||||
className="aftersales-signature-preview"
|
||||
onClick={() => openSignatureOverlay('responsible')}
|
||||
>
|
||||
<img src={responsibleSignatureData} alt="负责人签名" />
|
||||
<span className="aftersales-signature-preview-hint">点击可重新签名</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="aftersales-signature-trigger"
|
||||
onClick={() => openSignatureOverlay('responsible')}
|
||||
>
|
||||
<EditOutlined />
|
||||
<span>负责人签名</span>
|
||||
<small>将进入签名页</small>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{signatureData ? (
|
||||
<button
|
||||
type="button"
|
||||
className="aftersales-signature-preview"
|
||||
onClick={() => setShowSignatureOverlay(true)}
|
||||
>
|
||||
<img src={signatureData} alt="客户确认签名" />
|
||||
<span className="aftersales-signature-preview-hint">点击可重新签名</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="aftersales-signature-trigger"
|
||||
onClick={() => setShowSignatureOverlay(true)}
|
||||
>
|
||||
<EditOutlined />
|
||||
<span>点击此处签名</span>
|
||||
<small>将进入签名页</small>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="aftersales-actions">
|
||||
<Button
|
||||
@@ -288,11 +359,16 @@ function AftersalesConfirmPage() {
|
||||
</Card>
|
||||
|
||||
<SignatureOverlay
|
||||
open={showSignatureOverlay}
|
||||
onCancel={() => setShowSignatureOverlay(false)}
|
||||
open={activeSignatureRole !== null}
|
||||
title={activeSignatureRole === 'responsible' ? '负责人签名' : '客户签名'}
|
||||
onCancel={() => setActiveSignatureRole(null)}
|
||||
onConfirm={(url) => {
|
||||
setSignatureData(url);
|
||||
setShowSignatureOverlay(false);
|
||||
if (activeSignatureRole === 'responsible') {
|
||||
setResponsibleSignatureData(url);
|
||||
} else {
|
||||
setCustomerSignatureData(url);
|
||||
}
|
||||
setActiveSignatureRole(null);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -241,11 +241,11 @@ function AftersalesDetailPage() {
|
||||
.electronic-form-table td { min-height: 24px; }
|
||||
.electronic-form-table .electronic-form-code { color: #165dff; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 16px; font-weight: 700; }
|
||||
.electronic-form-text { min-height: 72px; white-space: pre-wrap; }
|
||||
.electronic-form-signature { display: flex; justify-content: flex-end; margin-top: 18px; }
|
||||
.electronic-form-signature-box { width: 240px; text-align: center; }
|
||||
.electronic-form-signatures { display: flex; justify-content: space-between; gap: 40px; margin-top: 22px; }
|
||||
.electronic-form-signature-box { flex: 1; max-width: 300px; text-align: center; }
|
||||
.electronic-form-signature-title { margin: 0 0 8px; font-weight: 600; }
|
||||
.electronic-form-signature-img { max-width: 220px; max-height: 90px; object-fit: contain; }
|
||||
.electronic-form-signature-line { height: 64px; border-bottom: 1px solid #1f2937; }
|
||||
.electronic-form-signature-stage { height: 96px; display: flex; align-items: center; justify-content: center; border-bottom: 1px solid #1f2937; }
|
||||
.electronic-form-signature-img { max-width: 240px; max-height: 90px; object-fit: contain; }
|
||||
</style>
|
||||
</head>
|
||||
<body>${formNode.outerHTML}</body>
|
||||
@@ -611,18 +611,30 @@ function AftersalesDetailPage() {
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="electronic-form-signature">
|
||||
<div className="electronic-form-signatures">
|
||||
<div className="electronic-form-signature-box">
|
||||
<p className="electronic-form-signature-title">负责人签名</p>
|
||||
<div className="electronic-form-signature-stage">
|
||||
{order.responsibleSignature ? (
|
||||
<img
|
||||
src={order.responsibleSignature}
|
||||
alt="负责人签名"
|
||||
className="electronic-form-signature-img"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="electronic-form-signature-box">
|
||||
<p className="electronic-form-signature-title">客户签名</p>
|
||||
{order.signature ? (
|
||||
<img
|
||||
src={order.signature}
|
||||
alt="客户签名"
|
||||
className="electronic-form-signature-img"
|
||||
/>
|
||||
) : (
|
||||
<div className="electronic-form-signature-line" />
|
||||
)}
|
||||
<div className="electronic-form-signature-stage">
|
||||
{order.signature ? (
|
||||
<img
|
||||
src={order.signature}
|
||||
alt="客户签名"
|
||||
className="electronic-form-signature-img"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,23 @@
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.aftersales-signature-section-title {
|
||||
margin: 0 0 14px;
|
||||
color: #111827;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.aftersales-signature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.aftersales-signature-item {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.aftersales-signature-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -53,6 +70,7 @@
|
||||
|
||||
.aftersales-signature-trigger {
|
||||
width: 100%;
|
||||
min-height: 132px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -88,6 +106,7 @@
|
||||
|
||||
.aftersales-signature-preview {
|
||||
width: 100%;
|
||||
min-height: 132px;
|
||||
padding: 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
@@ -102,8 +121,9 @@
|
||||
|
||||
.aftersales-signature-preview img {
|
||||
max-width: 100%;
|
||||
max-height: 160px;
|
||||
max-height: 120px;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.aftersales-signature-preview-hint {
|
||||
@@ -115,12 +135,16 @@
|
||||
margin-top: 24px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.aftersales-signature-archived-item {
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.aftersales-signature-archived-img {
|
||||
max-width: 100%;
|
||||
max-height: 220px;
|
||||
max-height: 160px;
|
||||
margin-top: 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
@@ -137,3 +161,9 @@
|
||||
.aftersales-actions > button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.aftersales-signature-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,14 +78,16 @@
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.electronic-form-signature {
|
||||
.electronic-form-signatures {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 18px;
|
||||
justify-content: space-between;
|
||||
gap: 40px;
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.electronic-form-signature-box {
|
||||
width: 240px;
|
||||
flex: 1;
|
||||
max-width: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -94,15 +96,18 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.electronic-form-signature-img {
|
||||
max-width: 220px;
|
||||
max-height: 90px;
|
||||
object-fit: contain;
|
||||
.electronic-form-signature-stage {
|
||||
height: 96px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #1f2937;
|
||||
}
|
||||
|
||||
.electronic-form-signature-line {
|
||||
height: 64px;
|
||||
border-bottom: 1px solid #1f2937;
|
||||
.electronic-form-signature-img {
|
||||
max-width: 240px;
|
||||
max-height: 90px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
@@ -131,4 +136,8 @@
|
||||
.electronic-form-table td {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.electronic-form-signatures {
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user