update style and add search form

This commit is contained in:
LittleBoy 2024-05-21 00:57:37 +08:00
parent 295d6c75e9
commit 61072a7382
16 changed files with 408 additions and 165 deletions

View File

@ -2,13 +2,12 @@ import AppRouter from "@/routes";
import {ConfigProvider} from "@/contexts/config"; import {ConfigProvider} from "@/contexts/config";
import {AuthProvider} from "@/contexts/auth"; import {AuthProvider} from "@/contexts/auth";
import '@/i18n/config.ts'; import '@/i18n/config.ts';
function App() { function App() {
return ( return (
<ConfigProvider> <ConfigProvider>
<AuthProvider> <AuthProvider>
<div className={'app'}> <AppRouter/>
<AppRouter/>
</div>
</AuthProvider> </AuthProvider>
</ConfigProvider> </ConfigProvider>
) )

View File

@ -30,6 +30,9 @@
.text-center{ .text-center{
text-align: center; text-align: center;
} }
.text-right{
text-align: right;
}
.space-between { .space-between {
justify-content: space-between; justify-content: space-between;
@ -53,6 +56,13 @@
.semi-dropdown-item-active { .semi-dropdown-item-active {
background-color: var(--semi-color-default-active); background-color: var(--semi-color-default-active);
} }
.semi-tabs{
&.no-border{
.semi-tabs-bar-line.semi-tabs-bar-top{
border-bottom: none;
}
}
}
.dashboard-menu-container { .dashboard-menu-container {
padding: var(--dashboard-layout-padding,15px); padding: var(--dashboard-layout-padding,15px);

View File

@ -0,0 +1,30 @@
.bill-search-form{
}
.bill-info {
font-size: 14px;
text-align: right;
line-height: 1.5;
.bill-info-item {
display: flex;
align-items: center;
justify-content: end;
.bill-info-title {
display: flex;
align-items: center;
&:before {
content: ' ';
width: 6px;
height: 6px;
display: block;
background-color: #00C479;
margin-right: 5px;
border-radius: 50%;
}
}
}
}

View File

@ -0,0 +1,43 @@
import styles from "@/pages/manual/manual.module.less";
import {Button, Space} from "@douyinfe/semi-ui";
import QRCode from "qrcode.react";
import {useTranslation} from "react-i18next";
import {useRef} from "react";
import {saveAs} from "file-saver";
const BillDetailItem = (item:{title:string;value:string})=>{
return <div className={styles.billDetailItem}>
<div className={styles.billDetailItemTitle}>{item.title} :</div>
<div className={styles.billDetailItemValue}>{item.value}</div>
</div>
}
const BillDetail:BasicComponent<{bill:BillModel}> = ()=>{
const {t} = useTranslation();
const qrCodeRef = useRef<HTMLDivElement>(null)
const downloadQRCode = ()=>{
const canvas = qrCodeRef.current?.querySelector('canvas');
if(!canvas) return
saveAs(canvas.toDataURL(), 'qrcode.png')
}
return <div>
<Space className={styles.billDetail} align={'start'}>
<div className={styles.billQrCode}>
<div className={styles.QRCodeContainer}>
<div className={styles.qrCode} ref={qrCodeRef}>
<QRCode size={250} value={'http://localhost:5173/pay?bill=123123123&from=qrcode'} />
</div>
</div>
<div className={styles.billExpTime}> {t('manual.exp_time')} {'12:00'} </div>
</div>
<div >
<BillDetailItem title={t('manual.bill_type')} value={'TUITION FEE'} />
<BillDetailItem title={t('manual.student_number')} value={'12345612'} />
<BillDetailItem title={t('manual.amount')} value={'HK$ 13600.00'} />
<Button onClick={downloadQRCode} style={{marginTop:20}} theme={'solid'} type={'primary'}>Download QR code</Button>
</div>
</Space>
</div>
}
export default BillDetail

View File

@ -1,75 +1,23 @@
import {Table, Typography} from "@douyinfe/semi-ui"; import {Table, Typography} from "@douyinfe/semi-ui";
import {ColumnProps} from "@douyinfe/semi-ui/lib/es/table"; import {ColumnProps} from "@douyinfe/semi-ui/lib/es/table";
import dayjs from "dayjs"; import React, {useMemo} from "react";
import React, {useMemo, useState} from "react";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import MoneyFormat from "@/components/money-format.tsx"; import MoneyFormat from "@/components/money-format.tsx";
import {clone} from "lodash"; import {Card} from "@/components/card";
import './bill.less'
import dayjs from "dayjs";
type BillListProps = { type BillListProps = {
type: 'query' | 'reconciliation'; type: 'query' | 'reconciliation';
operationRender?: (record: BillModel) => React.ReactNode; operationRender?: (record: BillModel) => React.ReactNode;
onRowSelection?: (selectedRowKeys?: (string | number)[]) => void; onRowSelection?: (selectedRowKeys?: (string | number)[]) => void;
} source: RecordList<BillModel>;
onPageChange: () => void;
const mockBill: BillModel = { loading?: boolean;
actual_payment_amount: 110,
amount: 110,
application_no: "123123",
apply_status: "pending",
bill_status: "pending",
created_at: new Date(),
currency: "HKD",
department: "经管学院",
expiration_time: dayjs().add(30, "minute").toDate(),
id: 1123123,
paid_area: "HongKong,China",
paid_at: new Date(),
pay_amount: 110,
pay_method: "WechatHk",
payment_channel: "AsiaPay",
program_id: 123123,
service_charge: 0,
student_en_name: "SanF Chung",
student_no: "123123",
student_sc_name: "张三丰",
student_semester: "1",
student_tc_name: "张三丰",
student_year: "2024",
updated_at: "",
detail: [
{amount: 55, bill_id: 1123123, id: 1, type: "APPLICATION FEE"},
{amount: 50, bill_id: 1123123, id: 1, type: "TUITION FEE"},
{amount: 5, bill_id: 1123123, id: 1, type: "VISA FEE"},
]
} }
export const BillList: React.FC<BillListProps> = (props) => { export const BillList: React.FC<BillListProps> = (props) => {
const {t, i18n} = useTranslation() const {t, i18n} = useTranslation()
const [data, setData] = useState<RecordList<BillModel>>({
list: Array(10).fill(mockBill).map((it, i) => {
const s = clone(it);
s.id = i;
if (i % 2 == 0) s.service_charge = 10
return s;
}),
pagination: {current: 1, pageSize: 10, total: 250}
});
console.log(data)
const [loading, setLoading] = useState(false);
const handlePageChange = (current: number) => {
setLoading(true)
setTimeout(() => {
setData({
...data,
pagination: {current, pageSize: 10, total: 250}
})
setLoading(false)
}, 500)
};
const scroll = useMemo(() => ({y: 600, x: 800}), []);
const columns = useMemo<ColumnProps<BillModel>[]>(() => { const columns = useMemo<ColumnProps<BillModel>[]>(() => {
const cols: ColumnProps<BillModel>[] = [ const cols: ColumnProps<BillModel>[] = [
@ -79,10 +27,30 @@ export const BillList: React.FC<BillListProps> = (props) => {
width: 80, width: 80,
fixed: true, fixed: true,
}, },
{
title: t('bill.title_paid_at'),
dataIndex: 'paid_at',
render: (value) => dayjs(value).format('YYYY-MM-DD'),
},
{
title: t('bill.title_program_id'),
dataIndex: 'program_id',
},
{
title: t('bill.title_department'),
dataIndex: 'department',
},
{
title: t('bill.title_year'),
dataIndex: 'student_year',
},
{
title: t('bill.title_semester'),
dataIndex: 'student_semester',
},
{ {
title: t('bill.title_student_name_en'), title: t('bill.title_student_name_en'),
dataIndex: 'student_en_name', dataIndex: 'student_en_name',
fixed: true,
}, },
{ {
title: t('bill.title_student_name_sc'), title: t('bill.title_student_name_sc'),
@ -92,9 +60,10 @@ export const BillList: React.FC<BillListProps> = (props) => {
{ {
title: t('bill.title_bill_detail'), title: t('bill.title_bill_detail'),
dataIndex: 'detail', dataIndex: 'detail',
width: 200, ellipsis: {showTitle: true},
render: (_, record) => (<div> width: 220,
{record.detail.map(it => (<div>{it.type}:{it.amount}</div>))} render: (_, record) => (<div style={{fontSize: 13, lineHeight: 1.1}}>
{record.detail.map((it,idx) => (<div key={idx}>{it.type}:<MoneyFormat money={it.amount}/></div>))}
</div>), </div>),
}, },
{ {
@ -157,31 +126,46 @@ export const BillList: React.FC<BillListProps> = (props) => {
return cols; return cols;
}, [props.operationRender, props.type, i18n.language]); }, [props.operationRender, props.type, i18n.language]);
const currentTotalAmount = useMemo(()=>{
// 计算当前列表总金额
return props.source.list.map(s=>s.amount).reduce((s,c)=>(s+c),0)
},[props.source])
return <div> return <Card
title={t('bill.title_bill_list')}
headerRight={<div className="bill-info">
<div className="bill-info-item">
<span className="bill-info-title">: </span>
<MoneyFormat money={props.source.pagination.recordTotal || 0}/>
</div>
<div className="bill-info-item">
<span className="bill-info-title">: </span>
<MoneyFormat money={currentTotalAmount || 0}/>
</div>
</div>}
>
<div className="bill-list-table"> <div className="bill-list-table">
<Table<BillModel> <Table<BillModel>
sticky={{top: 60}} sticky={{top: 60}}
bordered bordered
scroll={scroll} scroll={{x: 600,y:300,scrollToFirstRowOnChange:true}}
columns={columns} columns={columns}
dataSource={data.list} dataSource={props.source.list}
rowKey={'id'} rowKey={'id'}
pagination={{ pagination={{
currentPage: data.pagination.current, currentPage: props.source.pagination.current,
pageSize: data.pagination.pageSize, pageSize: props.source.pagination.pageSize,
total: data.pagination.total, total: props.source.pagination.total,
onPageChange: handlePageChange, onPageChange: props.onPageChange,
}} }}
loading={loading} loading={props.loading}
rowSelection={props.onRowSelection? { rowSelection={props.onRowSelection ? {
fixed: true, fixed: true,
onChange: (selectedRowKeys, _selectedRows) => { onChange: (selectedRowKeys) => {
props.onRowSelection?.(selectedRowKeys) props.onRowSelection?.(selectedRowKeys)
} }
}:undefined} } : undefined}
/> />
</div> </div>
</div> </Card>
} }

View File

@ -0,0 +1,61 @@
import {Button, Col, Form, Row} from "@douyinfe/semi-ui";
import React from "react";
import {Card} from "@/components/card";
type SearchFormProps = {
onSearch?: () => void;
showApply?: boolean;
searchHeader?: React.ReactNode;
searchFooter?: React.ReactNode;
}
const SearchForm: React.FC<SearchFormProps> = (props) => {
return (<Card style={{marginBottom: 20}}>
{props.searchHeader}
<div className="bill-search-form">
<Form>
<Row type={'flex'} gutter={20}>
<Col span={4}>
<Form.DatePicker type={'dateRange'} field="date" label='Transaction Date'>
</Form.DatePicker>
</Col>
<Col span={4}>
<Form.Input field='student_number' label='Student Number' trigger='blur'
placeholder='请输入姓名'/>
</Col>
<Col span={4}>
<Form.Input field='bill_number' label='Bill Number' trigger='blur'
placeholder='请输入姓名'/>
</Col>
<Col span={4}>
<Form.Select showClear field="pay_method" label='支付方式' placeholder='请支付方式' style={{width: '100%'}}>
<Form.Select.Option value="operate">AsiaPay</Form.Select.Option>
<Form.Select.Option value="rd">FlyWire</Form.Select.Option>
<Form.Select.Option value="pm">PPS</Form.Select.Option>
</Form.Select>
</Col>
<Col span={4}>
<Form.Select showClear field="bill_status" label='账单状态' placeholder='请选择账单状态' style={{width: '100%'}}>
<Form.Select.Option value="operate"></Form.Select.Option>
<Form.Select.Option value="rd"></Form.Select.Option>
<Form.Select.Option value="pm"></Form.Select.Option>
</Form.Select>
</Col>
{props.showApply && <Col span={4}>
<Form.Select showClear field="apply_status" label='对账状态' placeholder='请对账状态' style={{width: '100%'}}>
<Form.Select.Option value="operate"></Form.Select.Option>
<Form.Select.Option value="rd"></Form.Select.Option>
</Form.Select>
</Col>}
<Col span={4} style={{ display: 'flex', alignItems: 'flex-end',paddingBottom:12 }}>
<Button style={{width: 100}} htmlType={'submit'} theme={'solid'} type={'primary'}></Button>
</Col>
</Row>
</Form>
</div>
{props.searchFooter}
</Card>)
}
export default SearchForm;

View File

@ -1,14 +1,18 @@
.card-container { .card-container {
--card-padding: 10px 15px;
background-color: #ffffff; background-color: #ffffff;
border-radius: 5px; border-radius: 5px;
padding: 20px;
&.card-padding{
}
.card-header { .card-header {
padding: 10px 15px 30px 15px; margin-bottom: 10px;
color: #666; color: #666;
display: flex;
justify-content: space-between;
align-items: flex-start;
font-size: 20px;
} }
.card-body{ .card-body{
padding: 0px 15px 10px;
min-height: 100px;
} }
} }

View File

@ -1,16 +1,21 @@
import React from "react"; import React, {CSSProperties} from "react";
import './card.less' import './card.less'
import {clsx} from "clsx";
type CardProps = { type CardProps = {
title?: React.ReactNode; title?: React.ReactNode;
headerRight?: React.ReactNode;
children?: React.ReactNode; children?: React.ReactNode;
bordered?: boolean; bordered?: boolean;
style?: CSSProperties;
} }
export const Card: React.FC<CardProps> = (props) => { export const Card: React.FC<CardProps> = (props) => {
return (<div className={'card-container'}>
{props.title && <div className={'card-header'}> return (<div style={props.style} className={clsx('card-container',{'card-padding':!(props.title || props.headerRight)})}>
{(props.title || props.headerRight) && <div className={'card-header'}>
<div className="card-title">{props.title}</div> <div className="card-title">{props.title}</div>
<div className="card-right">{props.headerRight}</div>
</div>} </div>}
<div className="card-body">{props.children}</div> <div className="card-body">{props.children}</div>
</div>) </div>)

View File

@ -3,13 +3,19 @@
"title_actual_payment_amount": "Actually Paid", "title_actual_payment_amount": "Actually Paid",
"title_amount": "Amount", "title_amount": "Amount",
"title_bill_detail": "Bill Detail", "title_bill_detail": "Bill Detail",
"title_bill_list": "Bill List",
"title_bill_status": "Bill Status", "title_bill_status": "Bill Status",
"title_department": "Department",
"title_paid_at": "Transaction Date",
"title_pay_amount": "Pay Amount", "title_pay_amount": "Pay Amount",
"title_pay_method": "Pay Method", "title_pay_method": "Pay Method",
"title_program_id": "Program ID",
"title_reconciliation_status": "Reconciliation", "title_reconciliation_status": "Reconciliation",
"title_semester": "Semester",
"title_service_charge": "Service Charge", "title_service_charge": "Service Charge",
"title_student_name_en": "English Name", "title_student_name_en": "English Name",
"title_student_name_sc": "Chinese Name" "title_student_name_sc": "Chinese Name",
"title_year": "Year"
}, },
"error": { "error": {
"go_back": "Go Back", "go_back": "Go Back",

View File

@ -3,13 +3,19 @@
"title_actual_payment_amount": "实付金额", "title_actual_payment_amount": "实付金额",
"title_amount": "账单金额", "title_amount": "账单金额",
"title_bill_detail": "账单详情", "title_bill_detail": "账单详情",
"title_bill_list": "账单列表",
"title_bill_status": "账单状态", "title_bill_status": "账单状态",
"title_department": "学系",
"title_paid_at": "支付时间",
"title_pay_amount": "应付金额", "title_pay_amount": "应付金额",
"title_pay_method": "支付方式", "title_pay_method": "支付方式",
"title_program_id": "专业ID",
"title_reconciliation_status": "对账状态", "title_reconciliation_status": "对账状态",
"title_semester": "学期",
"title_service_charge": "手续费", "title_service_charge": "手续费",
"title_student_name_en": "英文名称", "title_student_name_en": "英文名称",
"title_student_name_sc": "中文名字" "title_student_name_sc": "中文名字",
"title_year": "学年"
}, },
"error": { "error": {
"go_back": "返回上一页", "go_back": "返回上一页",

View File

@ -1,40 +1,46 @@
{ {
"bill": { "bill": {
"title_actual_payment_amount": "实付金额", "title_actual_payment_amount": "實付金額",
"title_amount": "账单金额", "title_amount": "帳單金額",
"title_bill_detail": "账单详情", "title_bill_detail": "帳單詳情",
"title_bill_status": "账单状态", "title_bill_list": "帳單清單",
"title_pay_amount": "应付金额", "title_bill_status": "帳單狀態",
"title_pay_method": "支付方式", "title_department": "學系",
"title_reconciliation_status": "对账状态", "title_paid_at": "付款時間",
"title_service_charge": "手续费", "title_pay_amount": "應付金額",
"title_student_name_en": "英文名称", "title_pay_method": "付款方式",
"title_student_name_sc": "中文名字" "title_program_id": "專業ID",
"title_reconciliation_status": "對帳狀態",
"title_semester": "學期",
"title_service_charge": "手續費",
"title_student_name_en": "英文名稱",
"title_student_name_sc": "中文名字",
"title_year": "學年"
}, },
"error": { "error": {
"go_back": "返回上一页", "go_back": "返回上一",
"go_home": "回到首页" "go_home": "回到首"
}, },
"layout": { "layout": {
"logout": "注销登录", "logout": "登出登入",
"menu": { "menu": {
"bill": "账单查询", "bill": "帳單查詢",
"check": "对账", "check": "對帳",
"manual": "现场支付" "manual": "現場支付"
} }
}, },
"login": { "login": {
"submit": "使用SSO登入", "submit": "使用SSO登入",
"title": "登" "title": "登"
}, },
"manual": { "manual": {
"amount": "金", "amount": "金",
"amount_required": "请填写账单金额", "amount_required": "請填寫帳單金額",
"bill_type": "账单类型", "bill_type": "帳單類型",
"bill_type_required": "请选择账单类型", "bill_type_required": "請選擇帳單類型",
"btn_generate": "生成账单", "btn_generate": "產生帳單",
"exp_time": "账单过期时间", "exp_time": "帳單過期時間",
"student_number": "学号", "student_number": "學號",
"student_number_required": "请填写学号" "student_number_required": "請填入學號"
} }
} }

View File

@ -1,17 +1,77 @@
import {BillList} from "@/components/bill/list.tsx"; import {BillList} from "@/components/bill/list.tsx";
import {Button, Space} from "@douyinfe/semi-ui"; import {Button, Modal, Space} from "@douyinfe/semi-ui";
import {GeneratePdf} from "@/service/generate-pdf.ts"; import {GeneratePdf} from "@/service/generate-pdf.ts";
import {useState} from "react";
import {clone} from "lodash";
import dayjs from "dayjs";
import SearchForm from "@/components/bill/search-form.tsx";
import BillDetail from "@/components/bill/detail.tsx";
const mockBill: BillModel = {
actual_payment_amount: 110,
amount: 110,
application_no: "123123",
apply_status: "pending",
bill_status: "pending",
created_at: new Date(),
currency: "HKD",
department: "经管学院",
expiration_time: dayjs().add(30, "minute").toDate(),
id: 1123123,
paid_area: "HongKong,China",
paid_at: new Date(),
pay_amount: 110,
pay_method: "WechatHk",
payment_channel: "AsiaPay",
program_id: 123123,
service_charge: 0,
student_en_name: "SanF Chung",
student_no: "123123",
student_sc_name: "张三丰",
student_semester: "1",
student_tc_name: "张三丰",
student_year: "2024",
updated_at: "",
detail: [
{amount: 55, bill_id: 1123123, id: 1, type: "APPLICATION FEE"},
{amount: 50, bill_id: 1123123, id: 1, type: "TUITION FEE"},
{amount: 5, bill_id: 1123123, id: 1, type: "VISA FEE"},
]
}
const BillQuery = () => { const BillQuery = () => {
const [data, ] = useState<RecordList<BillModel>>({
list: Array(10).fill(mockBill).map((it, i) => {
const s = clone(it);
s.id = i;
if (i % 2 == 0) s.service_charge = 10
return s;
}),
pagination: {current: 1, pageSize: 10, total: 250,recordTotal:213123}
});
const [showBill,setShowBill] = useState<BillModel>()
const operation = (_record:BillModel)=>{ const operation = (_record:BillModel)=>{
return (<Space> return (<Space>
<Button size={'small'} theme={'solid'} type={'primary'}></Button> <Button size={'small'} theme={'solid'} type={'primary'}></Button>
<Button size={'small'} theme={'solid'} type={'primary'}></Button> <Button onClick={()=>setShowBill(_record)} size={'small'} theme={'solid'} type={'primary'}></Button>
<Button onClick={()=>GeneratePdf('111.pdf')} size={'small'} theme={'solid'} type={'primary'}></Button> <Button onClick={()=>GeneratePdf('111.pdf')} size={'small'} theme={'solid'} type={'primary'}></Button>
</Space>) </Space>)
} }
return (<div> return (<div>
<BillList type={'query'} operationRender={operation} /> <SearchForm showApply />
<BillList type={'query'} operationRender={operation} source={data} onPageChange={function (): void {
throw new Error("Function not implemented.");
}} />
<Modal
title="View QR code"
visible={!!showBill}
onOk={()=>{}}
onCancel={()=>setShowBill(undefined)} //>=1.16.0
closeOnEsc={true}
footerFill={true}
>
{showBill && <BillDetail bill={showBill} />}
</Modal>
</div>) </div>)
} }
export default BillQuery export default BillQuery

View File

@ -1,9 +1,22 @@
import {BillList} from "@/components/bill/list.tsx"; import {BillList} from "@/components/bill/list.tsx";
import {useState} from "react";
import SearchForm from "@/components/bill/search-form.tsx";
import {TabPane, Tabs } from "@douyinfe/semi-ui";
const BillReconciliation = () => { const BillReconciliation = () => {
const [data, ] = useState<RecordList<BillModel>>({
list: [], pagination: {current: 0, pageSize: 0, total: 0}
})
return (<div> return (<div>
<BillList type={'reconciliation'} onRowSelection={(keys)=>{
console.log('xxx',keys) <SearchForm searchHeader={<Tabs className={'no-border'}>
<TabPane tab={<span></span>} itemKey="1" />
<TabPane tab={<span></span>} itemKey="2" />
</Tabs>} />
<BillList source={data} type={'reconciliation'} onRowSelection={(keys) => {
console.log('xxx', keys);
}} onPageChange={function (): void {
throw new Error("Function not implemented.");
}} /> }} />
</div>) </div>)
} }

View File

@ -4,6 +4,7 @@ import {saveAs } from "file-saver"
import QRCode from "qrcode.react"; import QRCode from "qrcode.react";
import {useRef} from "react"; import {useRef} from "react";
import styles from './manual.module.less' import styles from './manual.module.less'
import {Card} from "@/components/card";
const BillDetailItem = (item:{title:string;value:string})=>{ const BillDetailItem = (item:{title:string;value:string})=>{
return <div className={styles.billDetailItem}> return <div className={styles.billDetailItem}>
@ -19,46 +20,48 @@ export default function Index() {
if(!canvas) return if(!canvas) return
saveAs(canvas.toDataURL(), 'qrcode.png') saveAs(canvas.toDataURL(), 'qrcode.png')
} }
return (<div className={styles.manualPage}> return (<Card>
<div className={styles.generateForm}> <div className={styles.manualPage}>
<Form layout='horizontal' onValueChange={values => console.log(values)}> <div className={styles.generateForm}>
<Form.Select <Form layout='horizontal' onValueChange={values => console.log(values)}>
field="bill_type" style={{width: 176}} <Form.Select
label={t('manual.bill_type')} field="bill_type" style={{width: 176}}
rules={[{required: true, message: t('manual.bill_type_required')}]} label={t('manual.bill_type')}
> rules={[{required: true, message: t('manual.bill_type_required')}]}
<Form.Select.Option value="admin">TUITION FEE</Form.Select.Option> >
<Form.Select.Option value="user">CAUTION FEE</Form.Select.Option> <Form.Select.Option value="admin">TUITION FEE</Form.Select.Option>
<Form.Select.Option value="guest">APPLICATION FEE</Form.Select.Option> <Form.Select.Option value="user">CAUTION FEE</Form.Select.Option>
</Form.Select> <Form.Select.Option value="guest">APPLICATION FEE</Form.Select.Option>
<Form.Input </Form.Select>
field='amount' label={t('manual.amount')} style={{minWidth: 120}} <Form.Input
rules={[{required: true, message: t('manual.amount_required')}]} field='amount' label={t('manual.amount')} style={{minWidth: 120}}
/> rules={[{required: true, message: t('manual.amount_required')}]}
<Form.Input />
field='student_number' label={t('manual.student_number')} style={{minWidth: 150}} <Form.Input
rules={[{required: true, message: t('manual.student_number_required')}]} field='student_number' label={t('manual.student_number')} style={{minWidth: 150}}
/> rules={[{required: true, message: t('manual.student_number_required')}]}
<div className={styles.generateButtonContainer}> />
<Button htmlType="submit">{t('manual.btn_generate')}</Button> <div className={styles.generateButtonContainer}>
</div> <Button htmlType="submit">{t('manual.btn_generate')}</Button>
</Form>
</div>
<Space className={styles.billDetail} align={'start'}>
<div >
<BillDetailItem title={t('manual.bill_type')} value={'TUITION FEE'} />
<BillDetailItem title={t('manual.student_number')} value={'12345612'} />
<BillDetailItem title={t('manual.amount')} value={'HK$ 13600.00'} />
<Button onClick={downloadQRCode} style={{marginTop:20}} theme={'solid'} type={'primary'}>Download QR code</Button>
</div>
<div className={styles.billQrCode}>
<div className={styles.QRCodeContainer}>
<div className={styles.qrCode} ref={qrCodeRef}>
<QRCode size={250} value={'http://localhost:5173/pay?bill=123123123&from=qrcode'} />
</div> </div>
</div> </Form>
<div className={styles.billExpTime}> {t('manual.exp_time')} {'12:00'} </div>
</div> </div>
</Space> <Space className={styles.billDetail} align={'start'}>
</div>) <div >
<BillDetailItem title={t('manual.bill_type')} value={'TUITION FEE'} />
<BillDetailItem title={t('manual.student_number')} value={'12345612'} />
<BillDetailItem title={t('manual.amount')} value={'HK$ 13600.00'} />
<Button onClick={downloadQRCode} style={{marginTop:20}} theme={'solid'} type={'primary'}>Download QR code</Button>
</div>
<div className={styles.billQrCode}>
<div className={styles.QRCodeContainer}>
<div className={styles.qrCode} ref={qrCodeRef}>
<QRCode size={250} value={'http://localhost:5173/pay?bill=123123123&from=qrcode'} />
</div>
</div>
<div className={styles.billExpTime}> {t('manual.exp_time')} {'12:00'} </div>
</div>
</Space>
</div>
</Card>)
} }

View File

@ -1,9 +1,15 @@
import {createBrowserRouter, Navigate, RouteObject, RouterProvider,} from "react-router-dom"; import {createBrowserRouter, Navigate, RouteObject, RouterProvider,} from "react-router-dom";
import {lazy, Suspense,} from "react"; import {lazy, Suspense, useMemo,} from "react";
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';
import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US';
import ErrorBoundary, {Error404} from "./error.tsx"; import ErrorBoundary, {Error404} from "./error.tsx";
import DashboardLayout from "@/routes/layout/dashboard-layout.tsx"; import DashboardLayout from "@/routes/layout/dashboard-layout.tsx";
import AuthLogin from "@/pages/auth/login.tsx"; import AuthLogin from "@/pages/auth/login.tsx";
import AuthLayout from "@/routes/layout/auth-layout.tsx"; import AuthLayout from "@/routes/layout/auth-layout.tsx";
import {useTranslation} from "react-i18next";
const ManualIndex = lazy(() => import("@/pages/manual/index.tsx")); const ManualIndex = lazy(() => import("@/pages/manual/index.tsx"));
const BillQuery = lazy(() => import("@/pages/bill/query.tsx")); const BillQuery = lazy(() => import("@/pages/bill/query.tsx"));
@ -57,10 +63,19 @@ const router = createBrowserRouter([
], {basename: import.meta.env.VITE_APP_BASE_NAME}) ], {basename: import.meta.env.VITE_APP_BASE_NAME})
const AppRouter = () => { const AppRouter = () => {
return (<Suspense> // change ui locale
<RouterProvider router={router}/> const {i18n} = useTranslation()
</Suspense>) const locale = useMemo(()=>{
// return (<RouterProvider router={router}/>) if(i18n.language === 'zh-CN') return zh_CN;
else if(i18n.language === 'zh-TW') return zh_TW;
return en_US;
},[i18n.language])
return (<LocaleProvider locale={locale}>
<Suspense>
<RouterProvider router={router}/>
</Suspense>
</LocaleProvider>)
} }
export default AppRouter; export default AppRouter;

View File

@ -96,9 +96,7 @@ type LayoutProps = {
} }
const LayoutContentContainer = styled.div({ const LayoutContentContainer = styled.div({
backgroundColor: '#fff',
borderRadius: 10, borderRadius: 10,
padding: 'var(--dashboard-layout-padding)',
marginTop:20 marginTop:20
}) })
export const BaseLayout: React.FC<LayoutProps> = ({children}) => { export const BaseLayout: React.FC<LayoutProps> = ({children}) => {