|
@@ -6,6 +6,9 @@ import { useUserStore } from '@/stores/user';
|
|
|
import { requestApi } from '@/services/apis';
|
|
import { requestApi } from '@/services/apis';
|
|
|
import './index.less';
|
|
import './index.less';
|
|
|
|
|
|
|
|
|
|
+// 支付宝小程序全局对象
|
|
|
|
|
+declare const my: any;
|
|
|
|
|
+
|
|
|
const precreateTradeApi = (data: { enterpriseId: string; totalAmount: string; subject?: string }) =>
|
|
const precreateTradeApi = (data: { enterpriseId: string; totalAmount: string; subject?: string }) =>
|
|
|
requestApi(
|
|
requestApi(
|
|
|
{
|
|
{
|
|
@@ -35,12 +38,32 @@ const STATUS_LABEL: Record<string, string> = {
|
|
|
TRADE_FINISHED: '交易完成',
|
|
TRADE_FINISHED: '交易完成',
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+/** 调用支付宝原生 API my.generateImageFromCode 生成二维码 base64 */
|
|
|
|
|
+const generateQRBase64 = (code: string, size: number): Promise<string> => {
|
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
|
+ if (typeof my === 'undefined' || typeof my.generateImageFromCode !== 'function') {
|
|
|
|
|
+ reject(new Error('my.generateImageFromCode 不可用(基础库需 ≥1.14.0)'));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ my.generateImageFromCode({
|
|
|
|
|
+ code,
|
|
|
|
|
+ format: 'QRCODE',
|
|
|
|
|
+ width: size,
|
|
|
|
|
+ height: size,
|
|
|
|
|
+ correctLevel: 'H',
|
|
|
|
|
+ success: (res: { image: string }) => resolve(res.image),
|
|
|
|
|
+ fail: (err: any) => reject(err),
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
export default function Receive() {
|
|
export default function Receive() {
|
|
|
const [statusBarHeight, setStatusBarHeight] = useState(0);
|
|
const [statusBarHeight, setStatusBarHeight] = useState(0);
|
|
|
const [amount, setAmount] = useState('');
|
|
const [amount, setAmount] = useState('');
|
|
|
const [loading, setLoading] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
|
const [qrCode, setQrCode] = useState('');
|
|
const [qrCode, setQrCode] = useState('');
|
|
|
const [qrImage, setQrImage] = useState('');
|
|
const [qrImage, setQrImage] = useState('');
|
|
|
|
|
+ const [qrError, setQrError] = useState('');
|
|
|
const [outTradeNo, setOutTradeNo] = useState('');
|
|
const [outTradeNo, setOutTradeNo] = useState('');
|
|
|
const [tradeStatus, setTradeStatus] = useState('');
|
|
const [tradeStatus, setTradeStatus] = useState('');
|
|
|
const [tradeAmount, setTradeAmount] = useState('');
|
|
const [tradeAmount, setTradeAmount] = useState('');
|
|
@@ -59,6 +82,22 @@ export default function Receive() {
|
|
|
};
|
|
};
|
|
|
}, []);
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
+ // qrCode 变化时自动调用官方 API 生成二维码
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (!qrCode) return;
|
|
|
|
|
+ setQrError('');
|
|
|
|
|
+ setQrImage('');
|
|
|
|
|
+
|
|
|
|
|
+ generateQRBase64(qrCode, 260)
|
|
|
|
|
+ .then((image) => {
|
|
|
|
|
+ setQrImage(image.startsWith('data:') ? image : 'data:image/png;base64,' + image);
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((e) => {
|
|
|
|
|
+ console.error('[QR] 生成失败', e);
|
|
|
|
|
+ setQrError((e && e.message) || '二维码生成失败');
|
|
|
|
|
+ });
|
|
|
|
|
+ }, [qrCode]);
|
|
|
|
|
+
|
|
|
const showToast = (options: ToastOptions) => {
|
|
const showToast = (options: ToastOptions) => {
|
|
|
Toast.show('receive-toast', options);
|
|
Toast.show('receive-toast', options);
|
|
|
};
|
|
};
|
|
@@ -79,16 +118,6 @@ export default function Receive() {
|
|
|
setAmount(filtered);
|
|
setAmount(filtered);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const generateQrImage = async (url: string) => {
|
|
|
|
|
- try {
|
|
|
|
|
- const QRCode = require('qrcode');
|
|
|
|
|
- const dataUrl = await QRCode.toDataURL(url, { width: 260, margin: 1 });
|
|
|
|
|
- setQrImage(dataUrl);
|
|
|
|
|
- } catch (_) {
|
|
|
|
|
- setQrImage(url);
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
const startPolling = (tradeNo: string) => {
|
|
const startPolling = (tradeNo: string) => {
|
|
|
if (pollingRef.current) clearInterval(pollingRef.current);
|
|
if (pollingRef.current) clearInterval(pollingRef.current);
|
|
|
pollingRef.current = setInterval(async () => {
|
|
pollingRef.current = setInterval(async () => {
|
|
@@ -129,8 +158,6 @@ export default function Receive() {
|
|
|
setQrCode(result.qrCode);
|
|
setQrCode(result.qrCode);
|
|
|
setOutTradeNo(result.outTradeNo);
|
|
setOutTradeNo(result.outTradeNo);
|
|
|
setTradeStatus('');
|
|
setTradeStatus('');
|
|
|
- setQrImage('');
|
|
|
|
|
- await generateQrImage(result.qrCode);
|
|
|
|
|
startPolling(result.outTradeNo);
|
|
startPolling(result.outTradeNo);
|
|
|
} catch (e: any) {
|
|
} catch (e: any) {
|
|
|
showToast({ icon: 'error', content: e.message || '生成收款码失败' });
|
|
showToast({ icon: 'error', content: e.message || '生成收款码失败' });
|
|
@@ -182,10 +209,12 @@ export default function Receive() {
|
|
|
<Text className="qr-title">客户扫码付款</Text>
|
|
<Text className="qr-title">客户扫码付款</Text>
|
|
|
|
|
|
|
|
<View className="qr-code">
|
|
<View className="qr-code">
|
|
|
- {qrImage && qrImage.startsWith('data:') ? (
|
|
|
|
|
|
|
+ {qrImage ? (
|
|
|
<Image src={qrImage} mode="aspectFit" style={{ width: 260, height: 260 }} />
|
|
<Image src={qrImage} mode="aspectFit" style={{ width: 260, height: 260 }} />
|
|
|
|
|
+ ) : qrError ? (
|
|
|
|
|
+ <Text style={{ color: '#ff4d4f', fontSize: 14 }}>{qrError}</Text>
|
|
|
) : (
|
|
) : (
|
|
|
- <Image src={qrCode} mode="aspectFit" style={{ width: 260, height: 260 }} />
|
|
|
|
|
|
|
+ <Text style={{ color: '#999', fontSize: 14 }}>二维码生成中…</Text>
|
|
|
)}
|
|
)}
|
|
|
</View>
|
|
</View>
|
|
|
|
|
|
|
@@ -213,6 +242,8 @@ export default function Receive() {
|
|
|
block
|
|
block
|
|
|
onClick={() => {
|
|
onClick={() => {
|
|
|
setQrCode('');
|
|
setQrCode('');
|
|
|
|
|
+ setQrImage('');
|
|
|
|
|
+ setQrError('');
|
|
|
setAmount('');
|
|
setAmount('');
|
|
|
setTradeStatus('');
|
|
setTradeStatus('');
|
|
|
}}
|
|
}}
|