This commit is contained in:
LittleBoy 2024-07-06 18:02:42 +08:00
parent 9a50d1c47d
commit 71b6f776c9
11 changed files with 74 additions and 32 deletions

View File

@ -51,5 +51,6 @@
"eslint-plugin-react-refresh": "^0.4.6",
"typescript": "^5.2.2",
"vite": "^5.2.0"
}
},
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
}

View File

@ -20,8 +20,8 @@ const BillDetailItems = (prop: { bill: BillModel }) => {
<BillDetailItem icon={<IconBillType/>} title={t('manual.bill_type')} value={billType}/>
<BillDetailItem icon={<IconStudentId/>} title={t('manual.student_number')} value={prop.bill.student_number || prop.bill.application_number}/>
<BillDetailItem icon={<IconStudentId/>} title={t('bill.title_student_name')}
value={`${prop.bill.student_english_name}/${prop.bill.student_chinese_name}`}/>
<BillDetailItem icon={<IconStudentEmail/>} title={'Email'} value={prop.bill.student_email}/>
value={`${prop.bill.student_english_name||'-'}${prop.bill.student_chinese_name?' / '+ prop.bill.student_chinese_name : ''}`}/>
<BillDetailItem icon={<IconStudentEmail/>} title={'Email'} value={prop.bill.student_email||'-'}/>
<BillDetailItem icon={<IconMoney/>} title={t('manual.amount')} value={<MoneyFormat money={prop.bill.amount}/>}/>
</>)
}

View File

@ -23,7 +23,7 @@ const BillDetail:BasicComponent<BillDetailProps> = ({bill,onCancel})=>{
</div>
</div>
<div className={'bill-info-detail'}>
<div className={'bill-exp-time text-center'}> {t('manual.exp_time')} {dayjs(bill.expiration_time).format('HH:mm')} </div>
<div className={'bill-exp-time text-center'}> {t('manual.exp_time')} {dayjs(bill.expiration_time).format('YYYY-MM-DD HH:mm')} </div>
<BillDetailItems bill={bill} />
</div>
</div>

View File

@ -1,12 +1,13 @@
import {Table, Typography} from "@douyinfe/semi-ui";
import {ColumnProps} from "@douyinfe/semi-ui/lib/es/table";
import React, {useMemo} from "react";
import React, {useMemo, useState} from "react";
import {useTranslation} from "react-i18next";
import dayjs from "dayjs";
import MoneyFormat from "@/components/money-format.tsx";
import {Card} from "@/components/card";
import './bill.less'
import {BillStatus} from "@/service/types.ts";
type BillListProps = {
type: 'query' | 'reconciliation';
@ -20,6 +21,31 @@ type BillListProps = {
export const BillList: React.FC<BillListProps> = (props) => {
const {t, i18n} = useTranslation()
const [currentTotalAmount,setCurrentTotalAmount] = useState(0)
const billStatusText = (billStatus: string) => {
switch (billStatus) {
case 'PENDING':
return t('bill.pay_status_pending')
case 'PAID':
return t('bill.pay_status_paid')
case 'CANCELLED':
return t('bill.pay_status_canceled')
default:
return billStatus
}
}
const applyStatusText = (status:string) => {
switch (status) {
case 'UNCHECKED':
return t('bill.reconciliation_status_pending')
case 'CHECKED':
return t('bill.reconciliation_status_submitted')
default:
return status
}
}
const columns = useMemo<ColumnProps<BillModel>[]>(() => {
const cols: ColumnProps<BillModel>[] = [
@ -138,7 +164,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
title: t('bill.title_bill_status'),
dataIndex: 'status',
width: 150,
// render: value => dayjs(value).format('YYYY-MM-DD'),
render: value => billStatusText(value),
},
]
if (props.type != 'reconciliation') {
@ -146,7 +172,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
title: t('bill.title_reconciliation_status'),
dataIndex: 'apply_status',
width: 150,
// render: value => dayjs(value).format('YYYY-MM-DD'),
render: value => applyStatusText(value),
})
}
if (props.operationRender) {
@ -161,9 +187,19 @@ export const BillList: React.FC<BillListProps> = (props) => {
return cols;
}, [props.operationRender, props.type, i18n.language]);
const currentTotalAmount = useMemo(() => {
// 计算当前列表总金额
return props.source?.list.map(s => Number(s.amount)).reduce((s, c) => (s + c), 0)
const isExpired = (bill: BillModel) => {
return bill.status == BillStatus.PENDING && dayjs(bill.expiration_time).isBefore(Date.now())
}
const currentList = useMemo(()=>{
const originList = props.source?.list || [];
originList.forEach(s => {
if(isExpired(s)){
s.status = BillStatus.EXPIRED;
}
})
const _total = originList.map(s=>Number(s.amount)).reduce((s, c) => (s + c), 0)
setCurrentTotalAmount(_total)
return originList;
},[props.source])
return <Card
@ -183,7 +219,7 @@ export const BillList: React.FC<BillListProps> = (props) => {
<Table<BillModel>
bordered
columns={columns}
dataSource={props.source?.list}
dataSource={currentList}
rowKey={'id'}
pagination={{
currentPage: props.source?.pagination.current,

View File

@ -74,16 +74,16 @@ const SearchForm: React.FC<SearchFormProps> = (props) => {
<Form<SearchFormFields> onSubmit={formSubmit}>
<Row type={'flex'} gutter={20}>
<Col xxl={4} xl={6} md={8}>
<Form.DatePicker type={'dateRange'} field="dateRange" label={t('bill.bill_date')}
<Form.DatePicker showClear type={'dateRange'} field="dateRange" label={t('bill.bill_date')}
style={{width: '100%'}}>
</Form.DatePicker>
</Col>
<Col xxl={4} xl={6} md={8}>
<Form.Input field='student_number' label={t('base.student_number')} trigger='blur'
<Form.Input showClear field='student_number' label={t('base.student_number')} trigger='blur'
placeholder={t('base.please_enter')}/>
</Col>
<Col xxl={4} xl={6} md={8}>
<Form.Input field='application_number' label={t('base.bill_number')} trigger='blur'
<Form.Input showClear field='application_number' label={t('base.bill_number')} trigger='blur'
placeholder={t('base.please_enter')}/>
</Col>
<Col xxl={4} xl={6} md={8}>

View File

@ -53,8 +53,6 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
isLoggedIn: !!user,
user:{
...user,
// TODO 等待接口返回
department:'root'
}
}
})
@ -71,7 +69,7 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
const login = async (code: string, state: string) => {
const user = await auth(code, state)
// 保存token
setAuthToken(user.token, user.exp)
setAuthToken(user.token, user.expiration_time?(new Date(user.expiration_time)).getTime():-1);
//
dispatch({
action: 'login',
@ -79,8 +77,6 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
isLoggedIn: true,
user:{
...user,
// TODO 等待接口返回
department:'root'
}
}
})
@ -105,6 +101,7 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
user: {
id: 1,
token: 'test-123123',
expiration_time: '',
email: 'test@qq.com',
department: 'root',
exp: 1,
@ -125,7 +122,7 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
user:{
...state.user,
...user
} as any,
} as never,
}
})

View File

@ -4,11 +4,14 @@ import {useSetState} from "ahooks";
import {useEffect} from "react";
import {createExternalBill} from "@/service/api/bill.ts";
import {IconLoading} from "@/components/icons";
import {FailIcon} from "@/assets/images/pay/fail.tsx";
import {PayLogo} from "@/pages/pay/component";
// 获取必填参数
const RequiredParams = [
// 'application_number','student_number', 可以不设置
'source', 'amount',
'source',
'amount',
'program_code',
'intake_year',
'intake_semester'
@ -36,39 +39,41 @@ const ExternalCreate = () => {
setState({loading: false})
navigate(`/pay?bill=${ret.id}`, {replace: true})
}).catch(() => {
setState({loading: false, 'error': 'create pay order error'})
setState({loading: false, 'error': 'create pay order failed'})
})
}
useEffect(() => {
if (searchParams) {
const paramsContent = searchParams.get('params');
if (!paramsContent) {
return setState({error: 'params error'})
return setState({error: 'params error',loading: false})
}
const params: ExternalCreateParamsType = JSON.parse(paramsContent);
for (let i = 0; i < RequiredParams.length; i++) {
const key = RequiredParams[i];
if (!params[key]) {
return setState({error: 'params error: require ' + key})
return setState({error: 'params error: require ' + key,loading: false})
}
params[key] = searchParams.get(key)
}
if (!params.application_number && !params.student_number) {
return setState({error: 'params error: require application_number or student_number'})
return setState({error: 'params error: require application_number or student_number',loading: false})
}
if (!params.details || params.details.length == 0) {
return setState({error: 'params error: require detail'})
return setState({error: 'params error: require detail',loading: false})
}
createBill(params)
return;
}
}, [searchParams])
return (<div className={`${styles.container} text-center`}>
<PayLogo/>
{state.loading && <div>
<div><IconLoading size={70} /></div>
<div></div>
</div>}
{state.error && <div>
<div style={{fontSize:100,color:'rgb(240, 86, 114)',margin:'30px 0 10px'}}><FailIcon /></div>
<h3>{state.error}</h3>
</div>}
</div>)

View File

@ -91,7 +91,7 @@ const PayIndex = () => {
</div>
</div>
<div className={styles.payConfirm}>
<div className="student-email">Your Email: {bill.student_email}</div>
{bill.student_email && <div className="student-email">Your Email: {bill.student_email}</div> }
<div className="pay-submit">
{ payChannel == 'asia_pay' && <StartAsiaPay bill={bill} />}
<StartFlyWire bill={bill} open={payChannel == 'flywire'} />

View File

@ -16,6 +16,8 @@ export class BizError extends Error {
export enum BillStatus {
PENDING= 'PENDING',
// 已过期
EXPIRED = 'EXPIRED',
PAID = 'PAID',
CANCELED = 'CANCELED',
}

1
src/types/auth.d.ts vendored
View File

@ -5,6 +5,7 @@ declare type UserProfile = {
username: string;
department: string;
exp: number;
expiration_time: string;
iat: number;
iss: string;
nbf: number;

View File

@ -14,7 +14,7 @@ export default defineConfig(({mode}) => {
API_PREFIX: process.env.APP_API_PREFIX || '/api',
FIY_WIRE_GATEWAY: process.env.FIY_WIRE_GATEWAY || 'https://gateway.flywire.com/v1/transfers',
SSO_AUTH_URL: process.env.SSO_AUTH_URL || 'https://portal.chuhai.edu.hk',
SSO_AUTH_CLIENT_KEY: process.env.AUTH_CLIENT_KEY || 'test_client_id',
SSO_AUTH_CLIENT_KEY: process.env.AUTH_CLIENT_KEY || 'payment',
AUTH_TOKEN_KEY: process.env.AUTH_TOKEN_KEY || 'payment-auth-token',
}),
AppMode: JSON.stringify(mode)
@ -28,7 +28,7 @@ export default defineConfig(({mode}) => {
port:10086,
proxy: {
'/api': {
target: 'http://43.136.175.109', //
target: 'https://test-payment-be.hkchc.team', //
changeOrigin: true,
//rewrite: (path) => path.replace(/^\/api/, '')
}