160 lines
5.1 KiB
TypeScript
160 lines
5.1 KiB
TypeScript
import { useRef, useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { request } from '../lib/api';
|
|
import { uploadRedeemPendingPhoto } from '../lib/upload';
|
|
import { isWechatEnv, weixinSdk } from '../lib/weixin';
|
|
import type { RedeemPendingSubmitResult } from '@dukang/shared-types';
|
|
|
|
type Props = {
|
|
redeemToken: string;
|
|
failCount: number;
|
|
};
|
|
|
|
export default function WeakNetFallbackPanel({ redeemToken, failCount }: Props) {
|
|
const navigate = useNavigate();
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const [uploading, setUploading] = useState(false);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [previewUrl, setPreviewUrl] = useState('');
|
|
const [photoResourceId, setPhotoResourceId] = useState('');
|
|
const [result, setResult] = useState<RedeemPendingSubmitResult | null>(null);
|
|
const [msg, setMsg] = useState('');
|
|
|
|
async function handleFile(file: File) {
|
|
setUploading(true);
|
|
setMsg('');
|
|
try {
|
|
const registered = await uploadRedeemPendingPhoto(file);
|
|
setPhotoResourceId(registered.id);
|
|
setPreviewUrl(registered.url);
|
|
} catch (e) {
|
|
setMsg(e instanceof Error ? e.message : '上传失败');
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
}
|
|
|
|
async function pickPhoto() {
|
|
setMsg('');
|
|
if (isWechatEnv()) {
|
|
try {
|
|
setUploading(true);
|
|
await weixinSdk.init();
|
|
const files = await weixinSdk.chooseImages({
|
|
count: 1,
|
|
sourceType: ['album', 'camera'],
|
|
});
|
|
if (files?.[0]) {
|
|
await handleFile(files[0]);
|
|
}
|
|
} catch (e) {
|
|
const text = e instanceof Error ? e.message : '选图失败';
|
|
if (!/cancel/i.test(text)) setMsg(text);
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
return;
|
|
}
|
|
inputRef.current?.click();
|
|
}
|
|
|
|
async function submitPending() {
|
|
if (!photoResourceId) {
|
|
setMsg('请先拍摄或上传核销码照片');
|
|
return;
|
|
}
|
|
setSubmitting(true);
|
|
setMsg('');
|
|
try {
|
|
const res = await request<RedeemPendingSubmitResult>('SHOP_H5', '/shop/redeem/pending', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
token: redeemToken,
|
|
photoResourceId,
|
|
failCount,
|
|
}),
|
|
});
|
|
setResult(res);
|
|
} catch (e) {
|
|
setMsg(e instanceof Error ? e.message : '提交失败');
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
}
|
|
|
|
function copyText(text: string) {
|
|
void navigator.clipboard?.writeText(text).then(
|
|
() => setMsg('已复制'),
|
|
() => setMsg('复制失败,请手动长按复制'),
|
|
);
|
|
}
|
|
|
|
return (
|
|
<section className="shop-weaknet-panel" role="alert">
|
|
<h3 className="headline-md" style={{ marginBottom: 8 }}>弱网核销兜底</h3>
|
|
<p className="body-md" style={{ marginBottom: 12, lineHeight: 1.5 }}>
|
|
扫码核销网络失败已达 {failCount} 次。请记录用户手机号尝试「手机号核销」,
|
|
或拍下用户核销码照片提交客服人工处理。
|
|
</p>
|
|
|
|
<div className="shop-weaknet-token">
|
|
<span className="label-md text-muted">核销码 ID(追查用)</span>
|
|
<button type="button" className="shop-weaknet-copy" onClick={() => copyText(redeemToken)}>
|
|
{redeemToken}
|
|
</button>
|
|
</div>
|
|
|
|
{result ? (
|
|
<div className="shop-weaknet-success">
|
|
<p className="body-md">已提交客服,待处理单号:</p>
|
|
<button type="button" className="shop-weaknet-copy" onClick={() => copyText(result.pendingNo)}>
|
|
{result.pendingNo}
|
|
</button>
|
|
<p className="label-md text-muted" style={{ marginTop: 8 }}>
|
|
请告知用户保留核销码编号,客服将尽快人工补核销。
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<input
|
|
ref={inputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
capture="environment"
|
|
hidden
|
|
onChange={(e) => {
|
|
const file = e.target.files?.[0];
|
|
if (file) void handleFile(file);
|
|
}}
|
|
/>
|
|
{previewUrl && (
|
|
<img src={previewUrl} alt="核销码照片预览" className="shop-weaknet-preview" />
|
|
)}
|
|
<div className="shop-weaknet-actions">
|
|
<button type="button" className="shop-redeem-confirm-btn" disabled={uploading} onClick={() => void pickPhoto()}>
|
|
{uploading ? '上传中…' : previewUrl ? '重新拍照' : '拍照 / 选图'}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="shop-redeem-confirm-btn"
|
|
disabled={submitting || !photoResourceId}
|
|
onClick={() => void submitPending()}
|
|
>
|
|
{submitting ? '提交中…' : '提交客服人工核销'}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="shop-btn-outline"
|
|
onClick={() => navigate('/redeem/phone')}
|
|
>
|
|
去手机号核销
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{msg && <p className="shop-redeem-error" style={{ marginTop: 12 }}>{msg}</p>}
|
|
</section>
|
|
);
|
|
}
|