✨ add page of payment
This commit is contained in:
parent
61072a7382
commit
914e9d5643
BIN
src/assets/images/auth/bg.jpg
Normal file
BIN
src/assets/images/auth/bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 271 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.5 MiB |
37
src/assets/images/pay/asia_pay.tsx
Normal file
37
src/assets/images/pay/asia_pay.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
const AsiaPayLogo = () => (
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 473 483.1" height="20">
|
||||
<path fill="#272E87" d="M178,249.9h-8.7c-12.7,1.1-24.9,5.1-35.6,11.9c-16.7,8.7-16.7,30.1-16.7,38v152.8c0,1.6,0,4,1.6,4
|
||||
c2.4-0.8,1.6,0,4-0.8l14.2-4c1.6,0,2.4-1.6,2.4-3.2v-60.2c7.1,4.8,15.8,9.5,30.1,10.3h5.6c34,0,49.1-35.6,49.1-77.6
|
||||
C223.9,284.7,209.7,249.9,178,249.9z M172.5,378.9h-3.2c-12.6-0.8-21.4-4.8-30.1-9.5v-80c0-8.7,11.9-19.5,30.9-20.3
|
||||
c1.3-0.2,2.7-0.3,4-0.3c23.8,0,25.3,28.5,25.3,53C199.4,350.4,197,378.9,172.5,378.9L172.5,378.9z"/>
|
||||
<path fill="#272E87" d="M291.1,251h-4.7c-18.2,0.8-41.2,8.7-41.2,13.5c-0.1,1.4,0.2,2.7,0.8,4l4,9.5c0.7,1.5,0.8,1.6,1.6,1.6
|
||||
c3.2,0,16.1-9.5,35.1-9.5c0.8,0,0.5,0.1,1.3,0.1c20.6,0,26.1,9.5,26.1,26.9v10.3c-9.3,0.2-18.5,1-27.7,2.4
|
||||
c-23,4-49.1,15.9-49.1,49.1c0,26.1,14.3,41.2,38,41.2h11.1c18.2-1.6,30.1-5.6,33.2-7.9c15-9.5,16.6-27.7,16.6-42.8v-57
|
||||
C336.2,266.9,324.4,251,291.1,251z M314.1,369c0,4.7-14.3,10.3-27.7,11.1h-4.8c-15.1,0-23-11.9-23-23.7
|
||||
c0-17.4,13.5-24.6,27.7-27.7c11.1-3.2,22.1-3.2,27.7-3.2L314.1,369z"/>
|
||||
<path fill="#272E87" d="M463,256.2c0-2.4,0.8-5.5-1.6-5.5l-15.8-0.8c-2.4,0-4,0-4,1.6l-0.8,118.8c-7.9,7.1-17.4,9.5-31.7,9.5
|
||||
c-18.2,0.8-29.3-18.2-30.1-41.2c-1.6-29.3,0-55.5,0-84.7c0-0.8-3.1-3.2-5.5-3.2h-14.2c0,0-3.2,4-3.2,4.8
|
||||
c0.8,15.8,6.3,114,10.3,121.1c1.6,2.4,4.7,7.1,7.9,11.1c3.2,3.2,7.1,5.5,11.9,8.7c10.3,6.3,42.8,4,55.4-7.1l0.8,13.5l-0.8,15
|
||||
c-0.8,15.1-0.8,39.6-23.8,40.4c-15,0-22.2-9.5-24.5-1.6c0,0.8-2.4,11.9-1.6,13.5c2.4,3.2,9.5,6.3,22.2,7.1
|
||||
c16.6,0.8,35.6-7.9,41.2-19.8c9.5-19,8.7-63.3,8.7-80L463,256.2z"/>
|
||||
<path fill="#F7A500" d="M201,82.8c0-1.6,0.8-2.4,0.8-3.2c0-1.6-0.8-3.2-3.1-4c-7.9-4-24.6-8.7-34.8-8.7c-26.1,0-44.4,16.6-44.4,41.2
|
||||
c0,44.3,64.9,37.2,64.9,68.1c0,14.2-10.3,21.4-23,21.4c-20.6,0-35.6-10.3-38-10.3c-0.8,0-2.4,0-3.2,2.4l-3.9,9.5
|
||||
c-0.8,0.8-0.8,1.6-0.8,3.2c0,5.5,30.1,14.3,44.3,14.3c25.3,0,47.5-14.3,47.5-42c0-45.9-65.7-36.4-65.7-68.9
|
||||
c0-14.2,10.3-19.8,22.2-19.8c15,0,27.7,7.9,29.3,7.9s2.4-0.8,4-3.2L201,82.8z"/>
|
||||
<path fill="#F7A500" d="M244.5,75.6c0-2.4-0.8-4-3.9-4h-15.1c-2.4,0-4,0.8-4,3.2v134.6c0,2.4,1.6,3.2,4,3.2h15.1
|
||||
c2.4,0,3.9-0.8,3.9-3.2V75.6z M249.3,19.4c-0.1-9.6-7.8-17.3-17.4-17.4c-10.3,0-18.2,7.9-18.2,17.4c-0.2,9.9,7.7,18,17.6,18.2
|
||||
c0.2,0,0.4,0,0.6,0C241.3,37.6,249.3,29.7,249.3,19.4"/>
|
||||
<path className="st1" fill="#F7A500" d="M320.5,67.7h-4.7c-18.2,0.8-41.2,8.7-41.2,13.5c-0.1,1.4,0.1,2.8,0.8,4l4,9.5c0.7,1.5,0.8,1.6,1.6,1.6
|
||||
c3.2,0,16.1-9.6,35.1-9.6c0.8,0,0.5,0.1,1.3,0.1c20.6,0,26.1,9.5,26.1,26.9V124c-9.3,0.2-18.5,1-27.7,2.4
|
||||
c-23,4-49.1,15.9-49.1,49.1c0,26.1,14.3,41.2,38,41.2h11.1c18.2-1.6,30.1-5.6,33.2-8c15-9.5,16.6-27.7,16.6-42.8v-57
|
||||
C365.7,83.6,353.8,67.7,320.5,67.7z M343.5,185.7c0,4.7-14.3,10.3-27.7,11.1H311c-15,0-23-11.9-23-23.8
|
||||
c0-17.4,13.5-24.6,27.7-27.7c11.1-3.2,22.2-3.2,27.7-3.2L343.5,185.7z"/>
|
||||
<path fill="#F7A500" d="M55.8,67.7h-4.7C32.9,68.5,9.9,76.4,9.9,81.2c-0.1,1.4,0.1,2.8,0.8,4l3.9,9.5c0.7,1.5,0.8,1.6,1.6,1.6
|
||||
c3.2,0,16.1-9.6,35.1-9.6c0.8,0,0.5,0.1,1.3,0.1c20.6,0,26.1,9.5,26.1,26.9V124c-9.3,0.2-18.5,1-27.7,2.4
|
||||
c-23,4-49.1,15.8-49.1,49.1c0,26.1,14.3,41.2,38,41.2h11.1c18.2-1.6,30.1-5.6,33.3-7.9c15-9.6,16.6-27.7,16.6-42.8v-57
|
||||
C101,83.6,89.1,67.7,55.8,67.7z M78.8,185.7c0,4.7-14.3,10.3-27.7,11.1h-4.8c-15,0-23-11.9-23-23.8c0-17.4,13.5-24.6,27.7-27.7
|
||||
c11.1-3.2,22.2-3.2,27.7-3.2L78.8,185.7z"/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default AsiaPayLogo
|
19
src/assets/images/pay/flywire.tsx
Normal file
19
src/assets/images/pay/flywire.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
const FlywireLogo = () => (
|
||||
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 161 56" width="50">
|
||||
<path d="M34.2,43.1L40.9,1h5.4l-6.7,42.1L34.2,43.1 M67.2,42.3c-1.3,8.2-6,12.5-13.5,12.5
|
||||
c-4.6,0-8.2-1.7-10.6-4.6l3-3.7c2.1,2.5,4.6,3.6,7.6,3.6c4.5,0,7.3-2.6,8.2-7.8l0.3-2.1h-0.1c-2.4,2.3-5.3,3.5-8.4,3.5
|
||||
c-5.8,0-8.6-2.9-8.6-8.5c0-1.1,0.1-2.2,0.3-3.4l2.7-17.3h5.3l-2.7,17.3c-0.2,0.9-0.2,1.7-0.2,2.4c0,3.2,1.4,4.7,4.5,4.7
|
||||
c3.3,0,6.2-2,8.1-5.5l3-19h5.4L67.2,42.3 M128,19.7l1.1-5.3h-5.3l-4.5,28.8h5.4l2.4-15.4c1.1-5.7,5.2-8.5,9.6-8.5
|
||||
c0.5,0,0.9,0,1.4,0l0.8-5.1C133.8,13.5,130,16.1,128,19.7 M107.6,14.6l-8.9,19.9l-3.2-19.9h-5.2l-9.1,19.9l-3-19.9h-5.5l4.6,28.7
|
||||
H83l8.9-19.9L95,43.3h5.5l12.8-28.7H107.6 M109.5,43.3l4.5-28.7h5.4l-4.5,28.7H109.5 M120.8,8.8c0,1.7-1.4,3.1-3.1,3.1
|
||||
s-3.1-1.4-3.1-3.1s1.4-3.1,3.1-3.1C119.5,5.7,120.8,7.1,120.8,8.8L120.8,8.8z M20.5,22.5l0.5-3.1H1.8l0.8-5h19.2l0.4-2.4
|
||||
C23.3,5,27.9,1,35,1h3.9l-0.8,4.9l-3.6,0c-3.9,0-6.1,1.9-6.8,6.3l-0.3,2.2h7.4l-0.8,5h-7.4l-4.1,24.2c-1.1,7-5.8,11-12.9,10.9
|
||||
l-0.4,0l0.8-5.3l0.3,0c4,0,6.1-1.7,6.8-6l2.6-15.7H0.4l0.8-5L20.5,22.5L20.5,22.5z M146.1,39L146.1,39c7.5-0.1,7.7-5.4,7.7-5.4h5
|
||||
c0,0-1.1,10-12.8,10c-3.6,0-6.6-1.2-8.3-3.4c-1.9-2.4-2.5-5.3-2-8.9l0.4-2.6c1.1-6.7,2.6-15.1,14.3-15.1c3.2,0,5.9,0.9,7.7,2.7
|
||||
c1.6,1.5,2.5,3.7,2.5,5.8c0,3.1-1.3,5.5-3.8,7c-2.6,1.6-5.8,1.9-8.2,1.9h-7.9c-0.5,2.8-0.2,4.9,0.9,6.1
|
||||
C142.6,38.5,144.1,39.1,146.1,39z M141.6,25.8L141.6,25.8l-0.2,0.9h7.3c4.6,0,6.7-1.4,6.7-4.4c0-2.5-2.1-4-5.4-4
|
||||
C145.4,18.3,142.5,21,141.6,25.8z" fill={'#147bd1'}/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default FlywireLogo
|
7
src/assets/images/pay/success.tsx
Normal file
7
src/assets/images/pay/success.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
export const SuccessIcon = () => (
|
||||
<svg className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="1em"
|
||||
height="1em">
|
||||
<path
|
||||
d="M666.272 472.288l-175.616 192a31.904 31.904 0 0 1-23.616 10.4h-0.192a32 32 0 0 1-23.68-10.688l-85.728-96a32 32 0 1 1 47.744-42.624l62.144 69.6 151.712-165.888a32 32 0 1 1 47.232 43.2m-154.24-344.32C300.224 128 128 300.32 128 512c0 211.776 172.224 384 384 384 211.68 0 384-172.224 384-384 0-211.68-172.32-384-384-384"
|
||||
fill="currentColor"></path>
|
||||
</svg>)
|
@ -37,7 +37,9 @@
|
||||
.space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.space-center{
|
||||
justify-content: center;
|
||||
}
|
||||
.align-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -48,9 +50,22 @@
|
||||
width: 1400px;
|
||||
margin: auto;
|
||||
}
|
||||
.main-bg-container{
|
||||
.main-content{
|
||||
|
||||
.auth-container {
|
||||
background: url(./images/auth/bg.png);
|
||||
}
|
||||
}
|
||||
.main-bg-container {
|
||||
background: url(./images/auth/bg.jpg);
|
||||
.main-content{
|
||||
display: flex;
|
||||
align-items:center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
backdrop-filter: blur(2px);
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
.semi-dropdown-item-active {
|
||||
@ -63,6 +78,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.semi-radio-inner{
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
.semi-radio-inner-display{
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
.semi-icon{
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-menu-container {
|
||||
padding: var(--dashboard-layout-padding,15px);
|
||||
|
@ -1,11 +1,29 @@
|
||||
import React from "react";
|
||||
|
||||
type MoneyFormatProps = {
|
||||
money:number|string;
|
||||
currency?:'USD'|'HKD'|'RMB';
|
||||
money: number | string;
|
||||
currency?: 'USD' | 'HKD' | 'RMB';
|
||||
}
|
||||
const MoneyFormat:React.FC<MoneyFormatProps> = ({money,currency = 'HKD'})=>{
|
||||
|
||||
function formatCurrency(amount: string | number) {
|
||||
// 将金额转换为字符串,并限制小数点后两位
|
||||
let amountStr = Number(amount).toFixed(2);
|
||||
|
||||
// 移除可能的负号
|
||||
const isNegative = amountStr[0] === '-';
|
||||
if (isNegative) {
|
||||
amountStr = amountStr.slice(1);
|
||||
}
|
||||
// 将金额字符串拆分为整数和小数部分
|
||||
const [intPart, decimalPart] = amountStr.split('.');
|
||||
// 整数部分每三位添加一个逗号
|
||||
const formattedIntPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
// 返回格式化后的金额
|
||||
return (isNegative ? '-' : '') + formattedIntPart + '.' + decimalPart;
|
||||
}
|
||||
|
||||
const MoneyFormat: React.FC<MoneyFormatProps> = ({money, currency = 'HKD'}) => {
|
||||
// 将货币数字转换为千分位格式且带2位小数
|
||||
return <span>{Number(money).toLocaleString('zh-HK', {style: 'currency', currency})}</span>
|
||||
return <span className={'money-format'}>{currency} {formatCurrency(money)}</span>
|
||||
}
|
||||
export default MoneyFormat;
|
@ -1,5 +1,7 @@
|
||||
{
|
||||
"bill": {
|
||||
"bill_number": "Bill Number",
|
||||
"download_receipt": "Download receipt",
|
||||
"title_actual_payment_amount": "Actually Paid",
|
||||
"title_amount": "Amount",
|
||||
"title_bill_detail": "Bill Detail",
|
||||
@ -42,5 +44,11 @@
|
||||
"exp_time": "bill will expires at",
|
||||
"student_number": "Student Number",
|
||||
"student_number_required": "required student number"
|
||||
},
|
||||
"pay": {
|
||||
"amount": "Amount",
|
||||
"charge": "Charge",
|
||||
"text_success": "Success",
|
||||
"total_amount": "Total Amount HKD"
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
{
|
||||
"bill": {
|
||||
"bill_number": "账单编号",
|
||||
"download_receipt": "下载收据",
|
||||
"title_actual_payment_amount": "实付金额",
|
||||
"title_amount": "账单金额",
|
||||
"title_bill_detail": "账单详情",
|
||||
@ -42,5 +44,11 @@
|
||||
"exp_time": "账单过期时间",
|
||||
"student_number": "学号",
|
||||
"student_number_required": "请填写学号"
|
||||
},
|
||||
"pay": {
|
||||
"amount": "应付金额",
|
||||
"charge": "手续费",
|
||||
"text_success": "支付成功",
|
||||
"total_amount": "应付总金额"
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
{
|
||||
"bill": {
|
||||
"bill_number": "账单编号",
|
||||
"download_receipt": "下载收据",
|
||||
"title_actual_payment_amount": "實付金額",
|
||||
"title_amount": "帳單金額",
|
||||
"title_bill_detail": "帳單詳情",
|
||||
@ -42,5 +44,11 @@
|
||||
"exp_time": "帳單過期時間",
|
||||
"student_number": "學號",
|
||||
"student_number_required": "請填入學號"
|
||||
},
|
||||
"pay": {
|
||||
"amount": "应付金额",
|
||||
"charge": "手续费",
|
||||
"text_success": "支付成功",
|
||||
"total_amount": "应付总金额"
|
||||
}
|
||||
}
|
@ -43,6 +43,9 @@ const AuthLogin = () => {
|
||||
</LogoContainer>
|
||||
<SubmitContainer>
|
||||
<Button theme='solid' type='primary' block onClick={showDashboard}>{t('login.submit')}</Button>
|
||||
<Button style={{marginTop:10}} theme='solid' type='primary' block onClick={()=>{
|
||||
navigate('/pay')
|
||||
}}>支付</Button>
|
||||
</SubmitContainer>
|
||||
</LoginContainer>)
|
||||
}
|
||||
|
10
src/pages/pay/component/index.tsx
Normal file
10
src/pages/pay/component/index.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import AppLogo from "@/assets/AppLogo.tsx";
|
||||
import useConfig from "@/hooks/useConfig.ts";
|
||||
|
||||
export const PayLogo = () => {
|
||||
const {appName} = useConfig()
|
||||
return (<div className={'align-center space-center'}>
|
||||
<AppLogo style={{fontSize:18}}/>
|
||||
{appName && <h2 style={{marginLeft:20}}>{appName}</h2>}
|
||||
</div>)
|
||||
}
|
83
src/pages/pay/index.tsx
Normal file
83
src/pages/pay/index.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import styles from './pay.module.less'
|
||||
import {PayLogo} from "@/pages/pay/component";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import MoneyFormat from "@/components/money-format.tsx";
|
||||
import {useState} from "react";
|
||||
import {Radio, RadioGroup} from '@douyinfe/semi-ui';
|
||||
import FlywireLogo from "@/assets/images/pay/flywire.tsx";
|
||||
import AsiaPayLogo from "@/assets/images/pay/asia_pay.tsx";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
const BillDetail = [
|
||||
{title: 'TUITION FEE', value: 67500},
|
||||
{title: 'CAUTION FEE', value: 400},
|
||||
{title: 'VISA FEE', value: 500},
|
||||
]
|
||||
const PayIndex = () => {
|
||||
const {t} = useTranslation()
|
||||
const [payChannel, setPayChannel] = useState('asia_pay')
|
||||
const navigate = useNavigate()
|
||||
const startPay = ()=>{
|
||||
navigate('/success?id=123123')
|
||||
}
|
||||
return (<div className={styles.container}>
|
||||
<PayLogo/>
|
||||
<div className={`${styles.payAmount} text-center`}>
|
||||
<div className="total">{t('pay.total_amount')}</div>
|
||||
<div className={styles.totalAmount}>
|
||||
<MoneyFormat money={69632.22}/>
|
||||
</div>
|
||||
<div className="space-center align-center">
|
||||
<div>
|
||||
<span>{t('pay.amount')}: </span>
|
||||
<span><MoneyFormat money={68400}/></span>
|
||||
</div>
|
||||
{payChannel == 'asia_pay' && <div style={{marginLeft: 10}}>
|
||||
<span>AsiaPay{t('pay.charge')}: </span>
|
||||
<span><MoneyFormat money={1232.2}/></span>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.payChanel}>
|
||||
<RadioGroup type={'card'} onChange={(e) => setPayChannel(e.target.value)} value={payChannel}>
|
||||
<Radio value={'asia_pay'}>
|
||||
<AsiaPayLogo/>
|
||||
<span style={{marginLeft: 5}}>AsiaPay</span>
|
||||
</Radio>
|
||||
<Radio value={'flywire'}>
|
||||
<FlywireLogo/>
|
||||
<span style={{marginLeft: 5}}>Flywire</span>
|
||||
</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className={styles.payDetail}>
|
||||
<div className="pay-item">
|
||||
<div className="title">Payment Type:</div>
|
||||
<div className="value">OFFER FEE</div>
|
||||
</div>
|
||||
{BillDetail.map((it, idx) => (
|
||||
<div key={idx} className="pay-item">
|
||||
<div className="title">{it.title}:</div>
|
||||
<div className="value"><MoneyFormat money={it.value}/></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.billInfo}>
|
||||
<div className="pay-item">
|
||||
<span className="title">Bill Number:</span>
|
||||
<span className="value">20240509114</span>
|
||||
</div>
|
||||
<div className="pay-item">
|
||||
<span className="title">Order Number:</span>
|
||||
<span className="value">PM-202405211114</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.payConfirm}>
|
||||
<div className="student-email">Your Email: xxxx@google.com</div>
|
||||
<div className="pay-submit">
|
||||
<button onClick={startPay} className={styles.btnConfirm}>CONFIRM PAY</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
export default PayIndex;
|
122
src/pages/pay/pay.module.less
Normal file
122
src/pages/pay/pay.module.less
Normal file
@ -0,0 +1,122 @@
|
||||
.border-radius {
|
||||
border-radius: var(--main-border-radius, 10px);
|
||||
}
|
||||
|
||||
.container {
|
||||
.border-radius;
|
||||
padding: 30px 80px;
|
||||
background: #fff;
|
||||
width: 600px;
|
||||
transition: width 0.1s;
|
||||
}
|
||||
|
||||
.payAmount {
|
||||
color: #fff;
|
||||
background-color: #50ADA7;
|
||||
.border-radius;
|
||||
padding: 25px;
|
||||
margin: 40px 0 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.totalAmount {
|
||||
font-size: 36px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.payChanel {
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.itemValue {
|
||||
|
||||
color: #999999;
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.billInfo {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
margin-top: 50px;
|
||||
|
||||
:global {
|
||||
.value {
|
||||
.itemValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.payDetail {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
|
||||
:global {
|
||||
.pay-item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
.itemValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.payConfirm {
|
||||
padding-top: 20px;
|
||||
margin-top: 20px;
|
||||
border-top: solid 1px #eee;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btnConfirm {
|
||||
width: 300px;
|
||||
height: 34px;
|
||||
background-color: #43ABFF;
|
||||
border-radius: 20px;
|
||||
color: #fff;
|
||||
outline: none;
|
||||
border: none;
|
||||
margin-top: 20px;
|
||||
cursor: pointer;
|
||||
|
||||
&:active, &:hover {
|
||||
background-color: #3d9dea;
|
||||
}
|
||||
}
|
||||
|
||||
.paySuccess {
|
||||
text-align: center;
|
||||
margin: 50px 0;
|
||||
:global{
|
||||
.icon{
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.successIcon {
|
||||
color: #74C041;
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.container {
|
||||
width: 80%;
|
||||
padding: 30px 30px;
|
||||
}
|
||||
|
||||
.payAmount {
|
||||
padding: 25px 10px;
|
||||
margin-top: 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
33
src/pages/pay/success.tsx
Normal file
33
src/pages/pay/success.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import styles from "@/pages/pay/pay.module.less";
|
||||
import {PayLogo} from "@/pages/pay/component";
|
||||
import {SuccessIcon} from "@/assets/images/pay/success.tsx";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {useDownloadReceiptPDF} from "@/service/generate-pdf.ts";
|
||||
import {Button} from "@douyinfe/semi-ui";
|
||||
|
||||
const PayIndex = () => {
|
||||
const {t} = useTranslation()
|
||||
const {downloadPDF} = useDownloadReceiptPDF()
|
||||
|
||||
return (<div className={styles.container}>
|
||||
<PayLogo/>
|
||||
<div className={styles.paySuccess}>
|
||||
<div className={styles.successIcon}>
|
||||
<SuccessIcon/>
|
||||
</div>
|
||||
<h2 className="pay-text">
|
||||
{t('pay.text_success')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className={styles.payConfirm}>
|
||||
<div className="student-email">{t('bill.bill_number')}: PM-202405211114</div>
|
||||
<div className="pay-submit">
|
||||
<Button
|
||||
theme={'solid'}
|
||||
onClick={() => downloadPDF({id: Date.now()})}
|
||||
style={{width:250,marginTop:20}}>{t('bill.download_receipt')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
export default PayIndex;
|
@ -1,5 +1,7 @@
|
||||
import {createBrowserRouter, Navigate, RouteObject, RouterProvider,} from "react-router-dom";
|
||||
import {lazy, Suspense, useMemo,} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
import { LocaleProvider } from '@douyinfe/semi-ui';
|
||||
import zh_CN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN';
|
||||
import zh_TW from '@douyinfe/semi-ui/lib/es/locale/source/zh_TW';
|
||||
@ -8,8 +10,11 @@ import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US';
|
||||
import ErrorBoundary, {Error404} from "./error.tsx";
|
||||
import DashboardLayout from "@/routes/layout/dashboard-layout.tsx";
|
||||
import AuthLogin from "@/pages/auth/login.tsx";
|
||||
import AuthLayout from "@/routes/layout/auth-layout.tsx";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import PayIndex from "@/pages/pay/index.tsx";
|
||||
import PaySuccess from "@/pages/pay/success.tsx";
|
||||
import MainLayout from "@/routes/layout/main-layout.tsx";
|
||||
|
||||
import Loader from "@/components/loader.tsx";
|
||||
|
||||
const ManualIndex = lazy(() => import("@/pages/manual/index.tsx"));
|
||||
const BillQuery = lazy(() => import("@/pages/bill/query.tsx"));
|
||||
@ -19,7 +24,7 @@ const BillReconciliation = lazy(() => import("@/pages/bill/reconciliation.tsx"))
|
||||
const routes: RouteObject[] = [
|
||||
{
|
||||
path: '/',
|
||||
element: <AuthLayout/>,
|
||||
element: <MainLayout/>,
|
||||
errorElement: <ErrorBoundary/>,
|
||||
children: [
|
||||
{
|
||||
@ -30,6 +35,14 @@ const routes: RouteObject[] = [
|
||||
path: 'login',
|
||||
element: <AuthLogin/>
|
||||
},
|
||||
{
|
||||
path: 'pay',
|
||||
element: <PayIndex/>
|
||||
},
|
||||
{
|
||||
path: 'success',
|
||||
element: <PaySuccess/>
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -72,7 +85,7 @@ const AppRouter = () => {
|
||||
},[i18n.language])
|
||||
|
||||
return (<LocaleProvider locale={locale}>
|
||||
<Suspense>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<RouterProvider router={router}/>
|
||||
</Suspense>
|
||||
</LocaleProvider>)
|
||||
|
@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
import {Outlet} from "react-router-dom";
|
||||
import styled from "@emotion/styled";
|
||||
import {I18nSwitcher} from "@/i18n";
|
||||
|
||||
const AuthContainer = styled.div({
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
})
|
||||
const I18nContainer = styled.div({
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
right: 10
|
||||
})
|
||||
const AuthLayout: React.FC<{ children?: React.ReactNode }> = ({children}) => {
|
||||
return (<AuthContainer className="auth-container">
|
||||
<I18nContainer>
|
||||
<I18nSwitcher/>
|
||||
</I18nContainer>
|
||||
{children ? children : <Outlet/>}
|
||||
</AuthContainer>)
|
||||
}
|
||||
export default AuthLayout
|
21
src/routes/layout/main-layout.tsx
Normal file
21
src/routes/layout/main-layout.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import {Outlet} from "react-router-dom";
|
||||
import styled from "@emotion/styled";
|
||||
import {I18nSwitcher} from "@/i18n";
|
||||
|
||||
const I18nContainer = styled.div({
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
right: 10
|
||||
})
|
||||
const MainLayout: React.FC<{ children?: React.ReactNode }> = ({children}) => {
|
||||
return (<div className="main-bg-container">
|
||||
<div className="main-content">
|
||||
<I18nContainer>
|
||||
<I18nSwitcher/>
|
||||
</I18nContainer>
|
||||
{children ? children : <Outlet/>}
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
export default MainLayout
|
@ -1,5 +1,7 @@
|
||||
import JsPDF from "jspdf";
|
||||
import autoTable from "jspdf-autotable";
|
||||
import {useState} from "react";
|
||||
|
||||
|
||||
function drawItem(doc: JsPDF, item: {
|
||||
title: string;
|
||||
@ -70,4 +72,23 @@ export function GeneratePdf(filename: string) {
|
||||
drawItem(doc, {title: 'Please retain this acknowledgement receipt for your record.'}, 185)
|
||||
|
||||
doc.save(filename);
|
||||
}
|
||||
|
||||
|
||||
// use service work generate pdf and download
|
||||
export function useDownloadReceiptPDF() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const downloadPDF = (bill: Partial<BillModel>) => {
|
||||
setLoading(true)
|
||||
setTimeout(() => {
|
||||
GeneratePdf(`Receipt-${bill.id}.pdf`)
|
||||
setLoading(false)
|
||||
}, 100);
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
downloadPDF
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import {defineConfig} from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import * as path from "path";
|
||||
import {resolve} from "path";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({mode}) => {
|
||||
@ -12,7 +12,7 @@ export default defineConfig(({mode}) => {
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src')
|
||||
'@': resolve(__dirname, './src')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user