✨ update style and add search form
This commit is contained in:
parent
295d6c75e9
commit
61072a7382
@ -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>
|
||||||
)
|
)
|
||||||
|
@ -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);
|
||||||
|
30
src/components/bill/bill.less
Normal file
30
src/components/bill/bill.less
Normal 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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/components/bill/detail.tsx
Normal file
43
src/components/bill/detail.tsx
Normal 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
|
@ -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>
|
||||||
}
|
}
|
61
src/components/bill/search-form.tsx
Normal file
61
src/components/bill/search-form.tsx
Normal 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;
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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>)
|
||||||
|
@ -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",
|
||||||
|
@ -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": "返回上一页",
|
||||||
|
@ -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": "請填入學號"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
@ -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>)
|
||||||
}
|
}
|
||||||
|
@ -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>)
|
||||||
}
|
}
|
@ -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;
|
@ -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}) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user