Upload site images during aftersales confirmation

This commit is contained in:
Frudrax Cheng
2026-06-02 11:04:38 +08:00
parent 99d8e87727
commit a19204d4b5
6 changed files with 164 additions and 0 deletions
+55
View File
@@ -7,6 +7,7 @@ import {
ClockCircleOutlined,
ExclamationCircleOutlined,
EditOutlined,
UploadOutlined,
} from '@ant-design/icons';
import { aftersalesApi } from '@/services/api';
import type { AftersalesPublicView, AftersalesServiceType } from '@/types';
@@ -34,6 +35,7 @@ function AftersalesConfirmPage() {
const [activeSignatureRole, setActiveSignatureRole] = useState<'customer' | 'responsible' | null>(
null,
);
const [uploadingImages, setUploadingImages] = useState(false);
const loadOrder = async () => {
setLoading(true);
@@ -119,6 +121,20 @@ function AftersalesConfirmPage() {
setActiveSignatureRole(role);
};
const handleUploadSiteImages = async (files: FileList | null) => {
if (!files || files.length === 0) return;
setUploadingImages(true);
try {
const images = await aftersalesApi.uploadSiteImages(serialNumber, Array.from(files));
setOrder((prev) => (prev ? { ...prev, siteImages: images } : prev));
message.success('现场图片上传成功');
} catch (err: any) {
message.error(err?.response?.data?.message || err.message || '上传现场图片失败');
} finally {
setUploadingImages(false);
}
};
if (loading) {
return (
<PublicLayout>
@@ -224,6 +240,18 @@ function AftersalesConfirmPage() {
<span className="value value-block">{order.resolutionNote}</span>
</div>
)}
{order.siteImages && order.siteImages.length > 0 && (
<div className="detail-item detail-item-block">
<span className="label"></span>
<div className="aftersales-site-images">
{order.siteImages.map((url) => (
<a key={url} href={url} target="_blank" rel="noreferrer">
<img src={url} alt="现场图片" />
</a>
))}
</div>
</div>
)}
{order.technicianName && (
<div className="detail-item">
<span className="label"></span>
@@ -265,6 +293,33 @@ function AftersalesConfirmPage() {
{isPending && (
<>
<div className="aftersales-upload-section">
<p className="aftersales-signature-section-title"></p>
<label className="aftersales-upload-trigger">
<UploadOutlined />
<span>{uploadingImages ? '上传中...' : '上传现场图片'}</span>
<small> 6 5MB</small>
<input
type="file"
accept="image/jpeg,image/png,image/webp,image/heic,image/heif"
multiple
disabled={uploadingImages}
onChange={(e) => {
handleUploadSiteImages(e.target.files);
e.currentTarget.value = '';
}}
/>
</label>
{order.siteImages && order.siteImages.length > 0 && (
<div className="aftersales-site-images">
{order.siteImages.map((url) => (
<a key={url} href={url} target="_blank" rel="noreferrer">
<img src={url} alt="现场图片" />
</a>
))}
</div>
)}
</div>
<div className="aftersales-signature-section">
<p className="aftersales-signature-section-title"></p>
<div className="aftersales-signature-grid">
+14
View File
@@ -251,6 +251,10 @@ function AftersalesDetailPage() {
.electronic-form-signature-title { flex: 0 0 auto; margin: 0 10px 0 0; font-weight: 600; white-space: nowrap; }
.electronic-form-signature-stage { flex: 1; height: 72px; min-width: 160px; display: flex; align-items: center; justify-content: center; border-bottom: 1px solid #1f2937; }
.electronic-form-signature-img { max-width: 180px; max-height: 68px; object-fit: contain; }
.electronic-form-site-images { margin-top: 18px; }
.electronic-form-site-images-title { margin: 0 0 10px; font-weight: 600; }
.electronic-form-site-images-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.electronic-form-site-images-grid img { width: 100%; height: 150px; object-fit: cover; border: 1px solid #1f2937; }
</style>
</head>
<body>${formNode.outerHTML}</body>
@@ -618,6 +622,16 @@ function AftersalesDetailPage() {
</tr>
</tbody>
</table>
{order.siteImages && order.siteImages.length > 0 && (
<div className="electronic-form-site-images">
<p className="electronic-form-site-images-title"></p>
<div className="electronic-form-site-images-grid">
{order.siteImages.map((url) => (
<img key={url} src={url} alt="现场图片" />
))}
</div>
</div>
)}
<div className="electronic-form-signatures">
<div className="electronic-form-signature-box">
<p className="electronic-form-signature-title"></p>
+58
View File
@@ -37,6 +37,12 @@
border-top: 1px solid rgba(0, 0, 0, 0.06);
}
.aftersales-upload-section {
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid rgba(0, 0, 0, 0.06);
}
.aftersales-signature-section-title {
margin: 0 0 14px;
color: #111827;
@@ -44,6 +50,58 @@
font-weight: 600;
}
.aftersales-upload-trigger {
min-height: 116px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
border: 1.5px dashed #94a3b8;
border-radius: 12px;
background: #f8fafc;
color: #1f2937;
cursor: pointer;
}
.aftersales-upload-trigger input {
display: none;
}
.aftersales-upload-trigger .anticon {
color: #1677ff;
font-size: 22px;
}
.aftersales-upload-trigger small {
color: #6b7280;
font-size: 12px;
}
.aftersales-site-images {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
gap: 8px;
width: 100%;
margin-top: 12px;
}
.aftersales-site-images a {
display: block;
aspect-ratio: 1;
overflow: hidden;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #ffffff;
}
.aftersales-site-images img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.aftersales-signature-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
+22
View File
@@ -115,6 +115,28 @@
object-fit: contain;
}
.electronic-form-site-images {
margin-top: 18px;
}
.electronic-form-site-images-title {
margin: 0 0 10px;
font-weight: 600;
}
.electronic-form-site-images-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.electronic-form-site-images-grid img {
width: 100%;
height: 150px;
object-fit: cover;
border: 1px solid #1f2937;
}
@media (max-width: 720px) {
.electronic-form-header {
align-items: flex-start;