168 lines
7.2 KiB
JavaScript
168 lines
7.2 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
|
import { Card, Tag, List, Button, Modal, Form, Selector, TextArea } from 'antd-mobile';
|
|
import { QRCode, Alert } from 'antd';
|
|
import Styles from './web.mobile.module.less';
|
|
import * as dayJs from 'dayjs';
|
|
import duration from 'dayjs/plugin/duration';
|
|
dayJs.extend(duration);
|
|
import { CentToString } from 'oak-domain/lib/utils/money';
|
|
import { PAY_CHANNEL_OFFLINE_NAME, PAY_CHANNEL_WECHAT_APP_NAME, PAY_CHANNEL_WECHAT_H5_NAME, PAY_CHANNEL_WECHAT_JS_NAME, PAY_CHANNEL_WECHAT_MP_NAME, PAY_CHANNEL_WECHAT_NATIVE_NAME } from '../../../types/PayConfig';
|
|
import { PayCircleOutline, GlobalOutline, InformationCircleOutline, CheckCircleOutline } from 'antd-mobile-icons';
|
|
export function RenderOffline(props) {
|
|
const { pay, t, offline, updateMeta, metaUpdatable } = props;
|
|
const { meta, iState } = pay;
|
|
return (<>
|
|
<Form layout="horizontal" style={{ width: '100%', marginTop: 12 }}>
|
|
{metaUpdatable && <Form.Item label={t("offline.label.tips")}>
|
|
<span style={{ wordBreak: 'break-all', textDecoration: 'underline' }}>{offline.tips}</span>
|
|
</Form.Item>}
|
|
<Form.Item label={t("offline.label.option")}>
|
|
{(metaUpdatable && !!(offline.options?.length)) ? <Selector value={meta?.option ? [meta?.option] : undefined} options={offline.options.map(ele => ({
|
|
label: ele,
|
|
value: ele,
|
|
}))} onChange={(v) => updateMeta({
|
|
...meta,
|
|
option: v[0],
|
|
})}/> : <span>{meta?.option}</span>}
|
|
</Form.Item>
|
|
<Form.Item label={t("offline.label.serial")}>
|
|
<TextArea autoSize={{ minRows: 3 }} value={meta?.serial} disabled={!metaUpdatable} placeholder={metaUpdatable ? t('offline.placeholder.serial') : t('offline.placeholder.none')} onChange={(value) => updateMeta({
|
|
...meta,
|
|
serial: value,
|
|
})}/>
|
|
</Form.Item>
|
|
</Form>
|
|
</>);
|
|
}
|
|
function Counter(props) {
|
|
const { deadline } = props;
|
|
const [counter, setCounter] = useState('');
|
|
const timerFn = () => {
|
|
const now = Date.now();
|
|
if (now < deadline) {
|
|
const duration = dayJs.duration(deadline - now);
|
|
setCounter(duration.format('HH:mm:ss'));
|
|
setTimeout(timerFn, 1000);
|
|
}
|
|
};
|
|
useEffect(() => {
|
|
timerFn();
|
|
}, []);
|
|
return (<div className={Styles.counter}>{counter}</div>);
|
|
}
|
|
function RenderWechatPay(props) {
|
|
const { pay, t } = props;
|
|
const { externalId, channel, timeoutAt, iState } = pay;
|
|
if (iState === 'paid') {
|
|
return (<div className={Styles.paid}>
|
|
<CheckCircleOutline fontSize={72} fontWeight="bold" color="green"/>
|
|
<div className={Styles.text}>
|
|
{t('success')}
|
|
</div>
|
|
</div>);
|
|
}
|
|
switch (channel) {
|
|
case PAY_CHANNEL_WECHAT_NATIVE_NAME: {
|
|
if (iState === 'paying') {
|
|
return (<>
|
|
<Counter deadline={timeoutAt}/>
|
|
<QRCode value={externalId} size={280}/>
|
|
<div className={Styles.qrCodeTips}>
|
|
{process.env.NODE_ENV === 'production' ?
|
|
<Alert type="info" message={t('wechat.native.tips')}/> :
|
|
<Alert type="warning" message={t('wechat.native.tips2')}/>}
|
|
</div>
|
|
</>);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function RenderPayMeta(props) {
|
|
const { pay, t, payConfig, updateMeta, metaUpdatable, notSameApp } = props;
|
|
const { iState, channel } = pay;
|
|
if (metaUpdatable && notSameApp) {
|
|
return <Alert type='warning' message={t('notSameApp')}/>;
|
|
}
|
|
switch (channel) {
|
|
case PAY_CHANNEL_OFFLINE_NAME: {
|
|
return (<>
|
|
{iState === 'paying' && <Alert type='info' message={t('offline.tips')}/>}
|
|
{RenderOffline({
|
|
pay,
|
|
t,
|
|
offline: payConfig.find(ele => ele.channel === PAY_CHANNEL_OFFLINE_NAME),
|
|
updateMeta,
|
|
metaUpdatable
|
|
})}
|
|
</>);
|
|
}
|
|
case PAY_CHANNEL_WECHAT_APP_NAME:
|
|
case PAY_CHANNEL_WECHAT_H5_NAME:
|
|
case PAY_CHANNEL_WECHAT_JS_NAME:
|
|
case PAY_CHANNEL_WECHAT_MP_NAME:
|
|
case PAY_CHANNEL_WECHAT_NATIVE_NAME: {
|
|
return <RenderWechatPay pay={pay} t={t}/>;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
export default function Render(props) {
|
|
const { pay, iStateColor, payConfig, notSameApp, type, startPayable, oakExecutable, onClose, closable, metaUpdatable } = props.data;
|
|
const { t, update, execute, clean, goBack } = props.methods;
|
|
if (pay) {
|
|
const { iState, channel, price } = pay;
|
|
let BtnPart = oakExecutable === true ? (<>
|
|
<div className={Styles.btnItem}>
|
|
<Button block color='primary' onClick={() => execute()}>
|
|
{t('common::action.update')}
|
|
</Button>
|
|
</div>
|
|
<div className={Styles.btnItem}>
|
|
<Button block onClick={() => clean()}>
|
|
{t('common::reset')}
|
|
</Button>
|
|
</div>
|
|
</>) : closable ? (<Button block color="primary" onClick={() => {
|
|
Modal.confirm({
|
|
title: t('cc.title'),
|
|
content: t('cc.content'),
|
|
onConfirm: async () => {
|
|
await execute('close');
|
|
onClose();
|
|
}
|
|
});
|
|
}}>
|
|
{t('pay:action.close')}
|
|
</Button>) : startPayable ? (<div>还没实现</div>) : (<Button block color="primary" onClick={goBack}>
|
|
{t('common::back')}
|
|
</Button>);
|
|
return (<div className={Styles.container}>
|
|
<Card title={t('title')} extra={<Tag color={iStateColor}>{t(`pay:v.iState.${iState}`)}</Tag>}>
|
|
<div>
|
|
<List>
|
|
<List.Item prefix={<InformationCircleOutline />} extra={t(`type.${type}`)}>
|
|
{t('type.label')}
|
|
</List.Item>
|
|
<List.Item prefix={<PayCircleOutline />} extra={CentToString(price, 2)}>
|
|
{t('pay:attr.price')}
|
|
</List.Item>
|
|
<List.Item prefix={<GlobalOutline />} extra={t(`payChannel::${channel}`)}>
|
|
{t('pay:attr.channel')}
|
|
</List.Item>
|
|
</List>
|
|
</div>
|
|
</Card>
|
|
<div className={Styles.meta}>
|
|
<RenderPayMeta pay={pay} t={t} payConfig={payConfig} updateMeta={(meta) => update({ meta })} metaUpdatable={metaUpdatable} notSameApp={notSameApp}/>
|
|
</div>
|
|
<div className={Styles.padding}/>
|
|
<div className={Styles.btn}>
|
|
{BtnPart}
|
|
</div>
|
|
</div>);
|
|
}
|
|
return null;
|
|
}
|